Wikilivres
frwikibooks
https://fr.wikibooks.org/wiki/Accueil
MediaWiki 1.47.0-wmf.21
first-letter
Média
Spécial
Discussion
Utilisateur
Discussion utilisateur
Wikilivres
Discussion Wikilivres
Fichier
Discussion fichier
MediaWiki
Discussion MediaWiki
Modèle
Discussion modèle
Aide
Discussion aide
Catégorie
Discussion catégorie
Transwiki
Discussion Transwiki
Wikijunior
Discussion Wikijunior
TimedText
TimedText talk
Module
Discussion module
Event
Event talk
Grec ancien/Lexique
0
29558
773421
772930
2026-09-28T07:16:22Z
~2026-50998-65
124625
Unification.
773421
wikitext
text/x-wiki
{{Grec ancien}}
{{Wiktionnaire|Catégorie:grec ancien}}
Au cours de vos lectures, vous serez amené à rencontrer des mots que vous n’arriverez pas à traduire en français. Le mieux est d’avoir un dictionnaire grec-français (type Bailly) mais certains mots reviennent souvent ; voici un lexique non-exhaustif grec-français par ordre alphabétique avec des renseignements sur le mot (genre, déclinaison, etc.)
[[Fichier:NAMA Stèle d'Hègèsô.jpg|left|170px|thumb|Stèle funéraire en marbre trouvée à Athènes. La défunte est Hègèsô, fille de Proxenos : la qualité du travail indique une famille noble. L’œuvre a été attribuée au sculpteur Callimaque. Musée archéologique d’Athènes, Grèce.]]
__FORCERSOMMAIRE__
==Α==
'''ἆ (interjection)''' : ah !<br>
'''ἃ ἅ (interjection)''' : ha ha.<br>
'''ἇ ἇ (interjection)''' : .<br>
'''ἀ- (préfixe)''' (Devient ''ἀν-'' devant un mot commençant par une voyelle.) : préfixe privatif.<br>
'''ἀάω (verbe)''' : Blesser, frapper. Frapper l’esprit. Égarer, tromper. S’égarer, commettre une faute.<br>
'''ἀϐαθέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἀϐαθής''.<br>
'''ἀϐαθέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἀϐαθής''.<br>
'''ἀϐαθής, -ής, -ές (adjectif)''' : superficiel.<br>
'''ἀϐαθῶς (adverbe)''' : superficiellement.<br>
'''ἀϐαρέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἀϐαρής''.<br>
'''ἀϐαρέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἀϐαρής''.<br>
'''ἀϐαρής, -ής, -ές (adjectif)''' : léger.<br>
'''ἀϐαρῶς (adverbe)''' : légèrement.<br>
'''ἄϐαξ, -κος (nom commun) (m)''' : Planche, tablette. Tableau de mathématicien. Table à jouer. Plat, assiette. Table pour compter les votes.<br>
'''ἀϐϐᾶς, -ᾶ (nom commun) (m)''' : abbé.<br>
'''αϐϐαεῖον, -ίου (nom commun) (n)''' : abbaye.<br>
'''ἀϐέλιος, -ίου (nom commun) (m)''' : Forme crétoise de ''ἥλιος''.<br>
'''ἀϐλαϐής, -ής, -ές (adjectif)''' : Inoffensif ; sûr.<br>
'''ἁϐός, -ή, -όν (adjectif)''' : Forme dorienne de ''ἡϐός''.<br>
'''ἁϐότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἁϐός''.<br>
'''ἁϐότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἁϐός''.<br>
'''ἁϐρός, -ά, -όν (adjectif)''' : tendre ; délicat.<br>
'''ἁϐρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἁϐρός''.<br>
'''ἁϐρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἁϐρός''.<br>
'''ἁϐρότης, -τος (nom commun) (f)''' : Bonheur, prospérité. Affectation. Magnificence, faste, opulence.<br>
'''ἁϐρῶς (adverbe)''' : tendrement ; délicatement.<br>
'''ἀγαθοεργία, -ας (nom commun) (f)''' : charité.<br>
'''ἀγαθός, -ή, -όν (adjectif)''' : Bon ; utile.<br>
'''ἀγαθωσύνη, -ης (nom commun) (f)''' : bonté, bienveillance.<br>
'''ἀγαθῶς (adverbe)''' : Bonnement ; utilement.<br>
'''ἀγαλλίασις, -άσεως (nom commun) (f)''' : jubilation.<br>
'''ἀγάλλω (verbe)''' : jubiler.<br>
'''ἄγαλμα, -άλματος (nom commun) (n)''' : statue.<br>
'''ἄγαν (adverbe)''' : trop.<br>
'''ἀγάπη, -ης (nom commun) (f)''' : amour divin, universel, inconditionnel.<br>
'''ἀγαπῶ (verbe)''' : aimer d’amour.<br>
'''ἀγασός, -ή, -όν (adjectif)''' : Forme dorienne de ''ἀγαθός''.<br>
'''ἀγγελιαφόρος, -ου (nom commun) (m)''' : messager.<br>
'''ἀγγελικός, -ή, -όν (adjectif)''' : d’ange.<br>
'''ἀγγελικῶς (adverbe)''' : angéliquement.<br>
'''ἀγγελικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀγγελικός''.<br>
'''ἀγγελικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀγγελικός''.<br>
'''ἄγγελος, -έλου (nom commun) (m)''' : messager ; ange.<br>
'''ἀγγέλλω (verbe)''' : annoncer.<br>
'''ἀγγίζω (verbe)''' : toucher.<br>
'''ἀγείρω (verbe)''' : assembler, rassembler.<br>
'''ἀγελάζω (verbe)''' : rassembler en troupeau.<br>
'''ἀγελαία, -ας (nom commun) (f)''' : .<br>
'''ἀγελαῖος, -ία, -ῖον (adjectif)''' : .<br>
'''ἀγελάς, -δος (nom commun) (f)''' : .<br>
'''ἀγέλαστος, -ος, -ον (adjectif)''' : maussade ; sombre.<br>
'''ἀγέλη, -ης (nom commun) (f)''' : troupeau.<br>
'''ἁγεμών, -όνος (nom commun) (m)''' : Forme dorienne de ''ἡγεμών''.<br>
'''ἁγίμων, -όνος (nom commun) (m)''' : Forme éolienne de ''ἡγεμών''.<br>
'''ἁγιάζω (verbe)''' : Bénir ; consacrer.<br>
'''ἁγιασμός, -οῦ (nom commun) (m)''' : bénédiction.<br>
'''ἅγιος, -ία, -ιον (adjectif)''' : auguste, sacré ; saint.<br>
'''ἁγίως (adverbe)''' : saintement.<br>
'''ἁγιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἅγιος''.<br>
'''ἁγιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἅγιος''.<br>
'''ἄγκιστρον, -ίστρου (nom commun) (n)''' : crochet.<br>
'''ἀγκυλίς, -δος (nom commun) (f)''' : crochet.<br>
'''ἀγκύλος, -η, -ον (adjectif)''' : courbé.<br>
'''ἀγκύλως (adverbe)''' : .<br>
'''ἀγκυλώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀγκύλος''.<br>
'''ἀγκυλώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀγκύλος''.<br>
'''ἄγκυρα, -ύρας (nom commun) (f)''' : ancre.<br>
'''ἀγκών, -ῶνος (nom commun) (m)''' : coude.<br>
'''ἀγλαός, -ός, -όν (adjectif)''' : brillant.<br>
'''ἁγνεία, -ας (nom commun) (f)''' : chasteté ; pureté. Purification ; raffinage<br>
'''ἁγνίζω (verbe)''' : être chaste.<br>
'''ἅγνισμα, -τος (nom commun) (n)''' : objet d'un sacrifice (ou offrande).<br>
'''ἁγνιστής, -οῦ (nom commun) (m)''' : .<br>
'''ἀγνόημα, -ήματος (nom commun) (n)''' : omission.<br>
'''ἄγνοια, -ίας (nom commun) (f)''' : ignorance.<br>
'''ἄγνος, -ου (nom commun) (m)''' : gattilier.<br>
'''ἁγνός, -ή, -όν (adjectif)''' : chaste ; pur.<br>
'''ἁγνότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἁγνός''.<br>
'''ἁγνότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἁγνός''.<br>
'''ἁγνότης, -τος (nom commun) (f)''' : chasteté ; pureté.<br>
'''ἁγνῶς (adverbe)''' : chastement ; purement.<br>
'''ἀγνοῶ (verbe)''' : ignorer.<br>
'''ἄγνωστος, -ος, -ον (adjectif)''' : (Sens passif) Inconnu, ignoré. (Sens actif) ignorant.<br>
'''ἀγνωστότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἄγνωστος''.<br>
'''ἀγνωστότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἄγνωστος''.<br>
'''ἀγνώστως (adverbe)''' : .<br>
'''ἀγορά, -ᾶς (nom commun) (f)''' : marché ; assemblée.<br>
'''ἀγορητής, -οῦ (nom commun) (m)''' : orateur.<br>
'''ἄγος, -ους (nom commun) (n)''' : Sacrilège, souillure. Homme sacrilège, impie. Expiation.<br>
'''ἀγριόχοιρος, -ίρου (nom commun) (m)''' : .<br>
'''ἀγροῖκος, -ίκα, -ῖκον (adjectif)''' : .<br>
'''ἄγριος, -ία, -ιον (adjectif)''' : qui vit dans les champs.<br>
'''ἀγρός, -οῦ (nom commun) (m)''' : (Au pluriel) Champ. (Au singulier) Ferme, bien de campagne, fonds, propriété foncière. La campagne (par opposition à la ville).<br>
'''ἄγρωστις, -ώστιδος (nom commun) (f)''' : chiendent officinal.<br>
'''ἄγυρις, -ύριος (nom commun) (f)''' : Forme éolienne de ''ἀγορά''.<br>
'''ἀγύρτης, -ου (nom commun) (m)''' : charlatan.<br>
'''ἀγών, -ῶνος (nom commun) (m)''' : Assemblée, réunion.<br>
'''ἀγωνία, -ας (nom commun) (f)''' : Lute dans les jeux, exercice, exercice gymnastique. (Figuré) Angoisse, anxiété.<br>
'''ἀγωνίζομαι (verbe)''' : Se battre, concourir pour un prix.<br>
'''ἀγωνιστής, -οῦ (nom commun) (m)''' : combattant.<br>
'''ἄγω (verbe)''' : Conduire, mener.<br>
'''ἀδαής, -ής, -ές (adjectif)''' : intrépide.<br>
'''ἀδάμας, -αντος (nom commun) (m)''' : diamant.<br>
'''ἀδελφή, -ῆς (nom commun) (f)''' : sœur.<br>
'''ἀδελφεά, -άς (nom commun) (f)''' : Forme dorienne de ''ἀδελφή''.<br>
'''ἀδελφεή, -ῆς (nom commun) (f)''' : Forme ionienne de ''ἀδελφή''.<br>
'''ἀδελφεός, -οῦ (nom commun) (m)''' : Forme homérique et ionienne de ''ἀδελφός''.<br>
'''ἀδελφειή, -ῆς (nom commun) (f)''' : Forme homérique de ''ἀδελφή''.<br>
'''ἀδελφιός, -οῦ (nom commun) (m)''' : Forme crétoise de ''ἀδελφός''.<br>
'''ἀδευφιός, -οῦ (nom commun) (m)''' : Autre forme crétoise de ''ἀδελφός''.<br>
'''ἀδελφός, -οῦ (nom commun) (m)''' : frère.<br>
'''ἀδεξιός, -ός, -όν (adjectif)''' : gauche ; maladroit.<br>
'''ἀδιάλλακτος, -ος, -ον (adjectif)''' : intransigeant.<br>
'''ἀδιανόητος, -ος, -ον (adjectif)''' : incompréhensible.<br>
'''ᾄδω (verbe)''' : Forme attique de ''ἀείδω''.<br>
'''ἀέ(ς) (adverbe)''' : Forme dorienne de ''ἀεί''.<br>
'''ἄεθλον, -ου (nom commun) (n)''' : Forme homérique de ''ἆθλον''.<br>
'''ἄεθλος, -ου (nom commun) (m)''' : Forme homérique et ionienne de ''ἆθλος''.<br>
'''ἀείδω (verbe)''' : chanter.<br>
'''ἀεικής, -ής, -ές (adjectif)''' : inapproprié ; malséant.<br>
'''ἀεικῶς (adverbe)''' : malséantement.<br>
'''ἀεικέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἀεικής''.<br>
'''ἀεικέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἀεικής''.<br>
'''ἀεί (adverbe)''' : toujours.<br>
'''ἀέλιος, -ίου (nom commun) (m)''' : Forme dorienne, éolienne, et arcado-chypriote de ''ἥλιος''.<br>
'''ἀεργός, -ή, -όν (adjectif)''' : .<br>
'''ἀετός, -οῦ (nom commun) (m)''' : aigle.<br>
'''ἄζα, -ης (nom commun) (f)''' : .<br>
'''ἀζαθός, -ή, -όν (adjectif)''' : Forme arcado-chypriote de ''ἀγαθός''.<br>
'''ἄζω (verbe)''' : .<br>
'''ἀηδών, -ονός (nom commun) (f)''' : rossignol.<br>
'''ἀήρ, -έρος (nom commun) (m)''' : air (que l'on respire).<br>
'''ἄθεος, -ος, -ον (adjectif)''' : athée.<br>
'''ἀθίγγανος, -ου (nom commun) (m)''' : tsigane.<br>
'''ἄθικτος, -ος, -ον (adjectif)''' : intact.<br>
'''ἄθλημα, -ήματος (nom commun) (n)''' : sport.<br>
'''ἀθλητής, -οῦ (nom commun) (m)''' : sportif.<br>
'''ἀθλητικός, -ή, -όν (adjectif)''' : sportif.<br>
'''ἀθλητικῶς (adverbe)''' : sportivement.<br>
'''ἀθλητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀθλητικός''.<br>
'''ἀθλητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀθλητικός''.<br>
'''ἀθλητικώτατα, -, - (adverbe)''' : Superlatif de ''ἀθλητικῶς''.<br>
'''ἀθλητικώτερον, -, - (adverbe)''' : Comparatif de ''ἀθλητικῶς''.<br>
'''ἄθλιος, -ία, -ιον (adjectif)''' : Concourant pour un prix ou luttant pour gagner un prix. Misérable, fieffé ; désolé.<br>
'''ἀθλίως (adverbe)''' : misérablement.<br>
'''ἄθλιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἄθλιος''.<br>
'''ἄθλιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἄθλιος''.<br>
'''ἆθλον, ἄθλου (nom commun) (n)''' : .<br>
'''ἆθλος, ἄθλου (nom commun) (m)''' : compétition.<br>
'''ἄθυρμα, -ύρματος (nom commun) (n)''' : jouet.<br>
'''ἀθύρω (verbe)''' : jouer.<br>
'''ἀθῷος, -ος, -ον (adjectif)''' : innocent.<br>
'''ἀθῷότης, -τος (nom commun) (f)''' : innocence.<br>
'''αἴ (interjection)''' : hélas.<br>
'''αἰϐοῖ (interjection)''' : beurk.<br>
'''αἰγαῖος, -ία, -ῖον (adjectif)''' : égéen.<br>
'''αἰγίθαλλος, -άλλου (nom commun) (m)''' : mésange.<br>
'''αἰγίς, -δος (nom commun) (f)''' : égide.<br>
'''αἴγλη, -ης (nom commun) (f)''' : splendeur.<br>
'''αἰδοΐα, -ας (nom commun) (f)''' : organe génital.<br>
'''αἰδοῖος, -ία, -ῖον (adjectif)''' : pudique.<br>
'''αἰδοῖον, -ίου (nom commun) (n)''' : vulve.<br>
'''αἰδώς, -οῦς (nom commun) (f)''' : pudeur.<br>
'''αἰεί (adverbe)''' : Forme ionienne de ''ἀεί''.<br>
'''αἰέν (adverbe)''' : Forme homérique de ''ἀεί''.<br>
'''αἰές (adverbe)''' : Autre forme dorienne de ''ἀεί''.<br>
'''αἰή (adverbe)''' : Autre forme dorienne de ''ἀεί''.<br>
'''αἰθάλη, -ης (nom commun) (f)''' : .<br>
'''αἴθαλος, -άλου (nom commun) (m)''' suie.<br>
'''αἰθήρ, -έρος (nom commun) (m/f)''' : éther.<br>
'''αἰθιοπικός, -ή, -όν (f)''' : éthiopien.<br>
'''αἶθος, -ἴθους (nom commun) (n)''' : Chaleur, feu.<br>
'''αἴθουσα, - (nom commun) (f)''' : .<br>
'''αἴθριος, -ια, -ιον (adjectif)''' : Clair, limpide ; beau.<br>
'''αἰθύλιον, -ίου (nom commun) (n)''' : éthyle.<br>
'''αἴθω (verbe)''' : brûler.<br>
'''αἶι (adverbe)''' : Forme éolienne de ''ἀεί''.<br>
'''-ακός, -ή, -όν (suffixe)''' : .<br>
'''αἴξ, -γός (nom commun) (f)''' : chèvre.<br>
'''αἰολικός, -ή, -όν (adjectif)''' : éolien.<br>
'''αἰόλος, -α, -ον (adjectif)''' : agité.<br>
'''αἰλουρίς, -δος (nom commun) (f)''' : chatte.<br>
'''αἴλουρος, -ύρου (m/f)''' : Chat, chatte.<br>
'''αἷμα, -ἵματος (nom commun) (n)''' : sang.<br>
'''αἱμωδία, -ας (nom commun) (f)''' : hémodie.<br>
'''αἱμωδιῶ (verbe)''' : saigner des dents.<br>
'''αἱμοπτύσις, -εως (nom commun) (f)''' : crachement de sang.<br>
'''αἱμορραγία, -ας (nom commun) (f)''' : perte de sang.<br>
'''αἱμορραγῶ (verbe)''' : perdre du sang.<br>
'''αἱμορροΐς, -δος (nom commun) (f)''' : hémorroïde.<br>
'''αἱμορροῶ (verbe)''' : saigner.<br>
'''αἱμόφυρτος, -ος, -ον (adjectif)''' : sanglant.<br>
'''-αινα, -ίνης (suffixe) (f)''' : .<br>
'''αἴνεσις, -έσεως (nom commun) (f)''' : louange.<br>
'''αἰνέω (verbe)''' : louer.<br>
'''αἴνιγμα, -ίγματος (nom commun) (n)''' : puzzle.<br>
'''αἰνιγματικός, -ή, -όν (adjectif)''' : relatif aux puzzles.<br>
'''αἰνίσσομαι (verbe)''' : parler par mystères.<br>
'''αἶνος, -ἵνου (nom commun) (m)''' : fable.<br>
'''αἰνῶ (verbe)''' : Parler de (suivi de l’accusatif). Trouver bon.<br>
'''-αῖος, -ία -ῖον (suffixe)''' : ancien.<br>
'''-αιότατος (suffixe)''' : Forme superlative de ''-αῖος''.<br>
'''-αιότερος (suffixe)''' : Forme comparative de ''-αῖος''.<br>
'''-αίως (suffixe)''' : Forme adverbiale de ''-αῖος''.<br>
'''αἰπόλος, -ου (nom commun) (m)''' : chevrier.<br>
'''αἶρα, -ἴρας (nom commun) (f)''' : action de prendre, prise ; choix.<br>
'''αἵρεσις, -έσεως (nom commun) (f)''' : action de prendre, prise ; choix.<br>
'''αἱρῶ (verbe)''' : Forme ionienne et poétique de ''αἴρω''.<br>
'''αἴρω (verbe)''' : Lever. (Par suite) Enlever, supprimer, détruire, faire périr. (Par extension) Contester, nier. (Figuré) Faire une levée. (Figuré) Élever, exalter, grandir. Mettre hors de soi. (Passif) Être transporté.<br>
'''αἶσα, -ἴσης (nom commun) (f)''' : Part ; destinée.<br>
'''αἴσθημα, -ήματος (nom commun) (n)''' : sentiment.<br>
'''αἴσθησις, -ήσεως (nom commun) (f)''' : Faculté de percevoir les sens, sensation. (Par extension) Action de percevoir l’intelligence, action de s’apercevoir. Organe des sens. L’un des cinq sens. Piste d’un animal.<br>
'''αἰσθητήριος, -ος, -ον (adjectif)''' : sensoriel.<br>
'''αἴσακος, -άκου (nom commun) (m)''' : .<br>
'''αἰσιοδοξία, -ας (nom commun) (f)''' : optimisme.<br>
'''αἶσχος, -ἴσχους (nom commun) (n)''' : honte.<br>
'''αἰσχρολογία, -ας (nom commun) (f)''' : obscénité.<br>
'''αἰσχρός, -ά, -όν (adjectif)''' : laid, honteux.<br>
'''αἰσχρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''αἰσχρός''.<br>
'''αἰσχρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''αἰσχρός''.<br>
'''αἰσχρῶς (adverbe)''' : honteusement.<br>
'''αἰσχύνη, -ης (nom commun) (f)''' : opprobre ; vergogne.<br>
'''αἰσχύνω (verbe)''' : déshonorer.<br>
'''αἰτέω (verbe)''' : Demander. (Dialogue) Poser un postulat.<br>
'''αἴτησις, -ήσεως (nom commun) (f)''' : demande.<br>
'''αἰχμαλωσία, -ας (nom commun) (f)''' : captivité.<br>
'''αἰχμαλωτίζω (verbe)''' : attraper ; capturer, saisir.<br>
'''αἰχμάλωτος, - (nom commun) (m)''' : captif.<br>
'''αἰών, -ῶνος (nom commun) (m)''' : temps (durée de la vie) ; moelle épinière.<br>
'''αἰώνιος, -ος, -ον (adjectif)''' : Éternel. Perpétuel. Séculaire.<br>
'''ἀΐω (verbe)''' : entendre.<br>
'''ἀκαδημία, -ας (nom commun) (f)''' : académie.<br>
'''ἀκακία, -ας (nom commun) (f)''' : innocence.<br>
'''ἄκανθα, -ης (nom commun) (f)''' : épine, piquant.<br>
'''ἀκανθοκνίδη, -ης (nom commun) (f)''' : ortie.<br>
'''ἀκανθόχοιρος, -ίρου (nom commun) (m)''' : porc-épic.<br>
'''ἀκαρής, -ής, -ές (adjectif)''' : insécable.<br>
'''ἄκαρι, -άρεως (nom commun) (n)''' : ciron ; mite.<br>
'''ἀκαριαῖος, -ία, -ῖον (adjectif)''' : instantané.<br>
'''ἀκίνδυνος, -η, -ον (adjectif)''' : inoffensif ; sûr.<br>
'''ἀκκίζομαι (verbe)''' : .<br>
'''ἀκκιστικός, -ή, -όν (adjectif)''' : .<br>
'''ἀκκισμός, -οῦ (nom commun) (m)''' : .<br>
'''ἄκμων, -ονος (nom commun) (m)''' : enclume.<br>
'''ἀκοίτης, -ου (nom commun) (m)''' : époux.<br>
'''ἄκοιτις, -ίτης (nom commun) (f)''' : épouse.<br>
'''ἀκοή, -ῆς (nom commun) (f)''' : ouïe.<br>
'''ἀκολασία, -ας (nom commun) (f)''' : débauche, luxure ; stupre.<br>
'''ἀκολουθία, -ας (nom commun) (f)''' : escorte ; suite.<br>
'''ἀκόλουθος, -ύθου (nom commun) (m)''' : domestique ; serviteur.<br>
'''ἀκολούθως (adverbe)''' : après ; ensuite.<br>
'''ἀκολουθῶ (verbe)''' : escorter ; suivre.<br>
'''ἀκόρεστος, -ος, -ον (adjectif)''' : (passif) Insatiable, inépuisable. (actif) Qui ne cause aucune satiété.<br>
'''ἀκουστικός, -ή, -όν (adjectif)''' : relatif à l'ouïe.<br>
'''ἀκούω (verbe)''' : écouter, entendre.<br>
'''ἀκρίς, -δος (nom commun) (f)''' : Sauterelle ; criquet.<br>
'''ἀκροϐάτης, -ου (nom commun) (m)''' : Danseur de corde, faiseur de tours d’agilité.<br>
'''ἀκροποσθία, -ας (nom commun) (f)''' : prépuce.<br>
'''ἀκρόπολις, -όλεως (nom commun) (f)''' : citadelle.<br>
'''ἀκροστιχίς, -δος (nom commun) (f)''' : acrostiche.<br>
'''ἄκρος, -α, -ον (adjectif)''' : extrême.<br>
'''ἄκρον -ου (nom commun) (n)''' : .<br>
'''ἀκτίς, -ῖνος (nom commun) (f)''' : rayon.<br>
'''ἀκύρωσις, -ώσεως (nom commun) (f)''' : .<br>
'''ἀκυρῶ (verbe)''' : .<br>
'''ἀλαζονεία, -ας (nom commun) (f)''' : .<br>
'''ἀλαζόνευμα, -ύματος (nom commun) (n)''' : .<br>
'''ἀλαζονεύομαι (verbe)''' : .<br>
'''ἀλαζονικός, -ή, -όν (adjectif)''' : .<br>
'''ἀλαζονίστατα (nom commun) (f)''' : .<br>
'''ἀλαζών, -όνος (nom commun) (m/f)''' : Imposteur, sans domicile fixe, escroc. Vantard.<br>
'''ἀλαζών, -ών, -όν (adjectif)''' : .<br>
'''ἀλάομαι (verbe)''' : vagabonder.<br>
'''ἀλάϐαστρος, -άστρου (nom commun) (m)''' : vase de plâtre.<br>
'''ἀλατιστός, -ή, -όν (adjectif)''' : .<br>
'''ἄλγος, -ους (nom commun) (n)''' : Souffrance. Douleur physique. Peine, affliction. Sujet de peine.<br>
'''ἀλείτης, -ου (nom commun) (m)''' : .<br>
'''ἄλειμμα, -ίμματος (nom commun) (n)''' : .<br>
'''ἀλειπτήριον, -ίου (nom commun) (m)''' : .<br>
'''ἀλειπτήρ, -ῆρος (nom commun) (m)''' : .<br>
'''ἀλείπτης ''' : .<br>
'''ἀλειπτός ''' : .<br>
'''ἄλειψις, -ῆρος (nom commun) (m)''' : .<br>
'''ἄλειφαρ, -ίφατος (nom commun) (n)''' : pommade.<br>
'''ἀλείφω (m)''' : pommader.<br>
'''ἀλεκτρυών, -όνος (nom commun) (m/f)''' : Coq ; poule.<br>
'''ἀλέκτωρ, -ορος (nom commun) (m)''' : coq.<br>
'''ἀλεξανδρῖνος, -ίνη, -ῖνον (adjectif)''' : alexandrin.<br>
'''ἀλέξω (verbe)''' : défendre. (prendre la défense)<br>
'''ἄλευρον, -ύρου (nom commun) (n)''' : farine de froment.<br>
'''ἀλευρώδης, -ώδης, -ῶδες (n)''' : semblable à de la farine de froment.<br>
'''ἀλέω (verbe)''' : moudre.<br>
'''ἄλη, -ης (nom commun)''' : errance.<br>
'''ἀλήθεια, -ίας (nom commun) (f)''' : vérité.<br>
'''ἀληθέστατος, -άτη, -έστατον (adjectif) (adjectif)''' : Superlatif de ''ἀληθής''.<br>
'''ἀληθέστερος, -έρα, -έτερον (adjectif)''' : Comparatif de ''ἀληθής''.<br>
'''ἀληθής, -ής, -ές (adjectif)''' : vrai.<br>
'''ἀληθινός, -ή, -όν (adjectif)''' : vrai.<br>
'''ἀληθινώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀληθινός''.<br>
'''ἀληθινώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀληθινός''.<br>
'''ἀληθινώτατα, -, - (adverbe)''' : Superlatif de ''ἀληθινῶς''.<br>
'''ἀληθινώτερον, -, - (adverbe)''' : Comparatif de ''ἀληθινῶς''.<br>
'''ἀληθινῶς (adverbe)''' : vraiment.<br>
'''ἀληθῶς (adverbe)''' : vraiment.<br>
'''ἀλήτης, -ου (nom commun) (m)''' : .<br>
'''ἁλιεύς, -έως (nom commun) (m)''' : pêcheur.<br>
'''ἁλιευτικός, -ή, -όν (adjectif)''' : qui concerne la pêche.<br>
'''ἁλιεύω (verbe)''' : pêcher.<br>
'''ἅλιος, -ίου (nom commun) (m)''' : Forme de ''ἥλιος''.<br>
'''ἀλιτρός, -ός, -όν (adjectif)''' : vilain.<br>
'''ἀλιτρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀλιτρός''.<br>
'''ἀλιτρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀλιτρός''.<br>
'''ἀλιτρῶς (adverbe)''' : vilainement.<br>
'''ἀλκή, -ῆς (nom commun) (f)''' : force.<br>
'''ἀλκμαῖος, -ία, -ῖον (adjectif)''' : jeune.<br>
'''ἀλλά (conjonction)''' : (Devient ''ἀλλ’'' devant un mot commençant par une voyelle.) Mais. D’un autre côté, autrement.<br>
'''ἀλλαγή, -ῆς (nom commun) (f)''' : Changement ; échange.<br>
'''ἀλλάσσω (verbe)''' : Changer, altérer. Échanger.<br>
'''ἄλλαξις, -άξεως (nom commun) (f)''' : troc.<br>
'''ἄλληλος (adjectif)''' : .<br>
'''ἀλληλούϊα (interjection)''' : alléluia.<br>
'''ἁλμάω (verbe)''' : saumurer.<br>
'''ἅλμη, -ης (nom commun) (f)''' : saumure.<br>
'''ἀλόη, -ης (nom commun) (f)''' : aloès.<br>
'''ἀλοιφή, -ῆς (nom commun) (f)''' : pommade.<br>
'''ἇλος, ἅλου (nom commun) (m)''' : Forme dorienne de ''ἧλος''.<br>
'''ἄλσος, -ους (nom commun) (n)''' : bois (lieu).<br>
'''ἅλς, -ός (nom commun) (m/f)''' : sel ; mer.<br>
'''ἅλυσις, -ύσεως (nom commun) (f)''' : chaîne (succession d’anneaux enserrés).<br>
'''ἄλυσις, -ύσεως (nom commun) (f)''' : détresse ; angoisse.<br>
'''ἄλυσσον, -ύσσου (nom commun) (n)''' : alysse.<br>
'''ἀλύω (verbe)''' : Être tout excité ; divaguer.<br>
'''ἄλφα (nom commun) (n)''' : alpha.<br>
'''ἀλφάϐητος, -ήτου (nom commun) (m)''' : Ensemble des lettres servant à écrire.<br>
'''ἄλφιτα, -ίτας (nom commun) (f)''' : farine d’orge.<br>
'''ἀλφός, -οῦ (nom commun) (m)''' : lèpre blanche.<br>
'''ἀλωπεκῆ, -ῆς (nom commun) (f)''' : peau de renard ; ruse.<br>
'''ἀλωπεκία, -ας (nom commun) (f)''' : chute des cheveux.<br>
'''ἀλωπεκίασις, -άσεως (nom commun) (f)''' : chute des cheveux.<br>
'''ἀλωπεκίς, -δος (nom commun) (f)''' : casquette en peau de renard.<br>
'''ἀλώπηξ, -εκος (nom commun) (f)''' : renard.<br>
'''ἅμμα, -τος (nom commun) (n)''' : nœud.<br>
'''ἅμαξα, -ης (nom commun) (f)''' : chariot.<br>
'''ἁμαξαία, -ας (nom commun) (f)''' : .<br>
'''ἁμαξαῖος, -ία, -ῖον (adjectif)''' : .<br>
'''ἁμαξακάρινον, -ίνου (nom commun) (n)''' : .<br>
'''ἁμαξάρχης, -ου (nom commun) (m)''' : .<br>
'''ἀμαυρός, -ά, -όν (adjectif)''' : sombre ; obscur.<br>
'''ἀμαύρωσις, -ώσεως (nom commun) (f)''' : obscurcissement.<br>
'''ἀμαυρῶ (verbe)''' : s’obscurcir.<br>
'''ἁμαρτάς, -δος (nom commun) (f)''' : Faute ; erreur, méprise.<br>
'''ἁμαρτάνω (verbe)''' : Manquer le but. Se tromper, se méprendre. Commettre une faute, faillir, pécher.<br>
'''ἁμάρτημα, -ήματος (nom commun) (n)''' : Échec ; faute. Péché.<br>
'''ἁμαρτία, -ας (nom commun) (f)''' : Erreur ; faute. Péché<br>
'''ἀμάτωρ, -ωρ, -ορ (adjectif)''' : Forme dorienne de ''ἀμήτωρ''.<br>
'''ἀμϐλύς, -εῖα, -ύ (adjectif)''' : émoussé.<br>
'''ἀμϐρόσιος, -ία, -όσιον (adjectif)''' : divin ; immortel.<br>
'''ἀμϐροσίως (adverbe)''' : divinement ; immortellement.<br>
'''ἀμϐροσιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀ μϐρόσιος''.<br>
'''ἀμϐροσιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀμϐρόσιος''.<br>
'''ἄμϐων, -ος (nom commun) (m)''' : Bord arrondi d’un vase.<br>
'''ἀμέθυστος, -ος, -ον (adjectif)''' : sobre.<br>
'''ἀμεθύω (verbe)''' : être sobre.<br>
'''ἀμέλγω (verbe)''' : Traire. Sucer, boire.<br>
'''ἀμελής, -ής, -ής (adjectif)''' : négligeant.<br>
'''ἀμελῶ (verbe)''' : négliger.<br>
'''ἀμέρα, -ας (nom commun) (f)''' : Forme dorienne de ''ἡμέρα''.<br>
'''ἀμήν (adverbe)''' : amen.<br>
'''ἀμήτωρ, -ωρ, -ορ (adjectif)''' : sans mère.<br>
'''ἀμίς, -δος (nom commun) (f)''' : pot de chambre.<br>
'''ἀμνάς, -δος (nom commun) (f)''' : agnelle.<br>
'''ἀμνός, -οῦ (nom commun) (m)''' : agneau.<br>
'''ἄμεσος (adverbe)''' : .<br>
'''ἀμπέλιον, -ίoυ (nom commun) (n)''' : petite vigne.<br>
'''ἄμπελος, -έλου (nom commun) (m)''' : vigne (plante).<br>
'''ἀμπελών, -ος (nom commun) (m)''' : vigne (endroit où elle est plantée).<br>
'''ἀμπρακικός, -η, -όν (adjectif)''' : ambracien.<br>
'''ἀμυγδάλη, -ης (nom commun) (f)''' : amygdale.<br>
'''ἀμύγδαλον, -άλoυ (nom commun) (n)''' : amande. (fruit)<br>
'''ἄμυλος, -ύλoυ (nom commun) (m)''' : amidon.<br>
'''ἀμύνω (verbe)''' : défendre.<br>
'''ἀμφί (adverbe ; préposition)''' : Autour. Séparément ; pour soi. Autour. À partir de ; loin de. (Avec le génitif)
Autour de ; au milieu de. (Avec le datif) Autour de (quelque chose). (Figuré) Au sujet de ; par suite de. (Avec l’accusatif) Autour de. (Par extension) En faisant le tour de : en circulant à travers ; à travers ; par. Au sujet de. Aux environs de. (Joint à ''περί'') Tout autour de.<br>
'''ἀμφί- (préfixe)''' : De deux côtés ; en double. Tout autour de. Au sujet de.<br>
'''ἀμφιθέατρον, -ου (nom commun) (n)''' : amphithéâtre.<br>
'''ἀμφιθέητρον, -ου (nom commun) (n)''' : Forme ionienne de ''ἀμφιθέατρον''.<br>
'''ἀμφισϐητήσιμος, -η, -ον (adjectif)''' : contestable.<br>
'''ἀμφισϐητῶ (n)''' : contester.<br>
'''ἀμφί (adverbe ; préposition)''' : Des deux côtés ; aux deux extrémités. Autour. À partir de ; loin de.<br>
'''ἀμφίς (adverbe ; préposition)''' : Des deux côtés ; aux deux extrémités. Autour. À partir de ; loin de.<br>
'''ἀμφορεύς, -έως (nom commun) (m)''' : amphore.<br>
'''ἀμφότερος, -έρα, -ότερον (adjectif)''' : l’un l’autre, les deux.<br>
'''ἄμφω (déterminant)''' : les deux.<br>
'''ἀνά (adverbe, préposition)''' : En haut. En avant. En haut de, sur, à travers.<br>
'''ἄνα- (préfixe)''' : De bas en haut. En arrière. (Par suite) faire le contraire. (Par extension) Faire de nouveau.<br>
'''ἀναϐάλλω (verbe)''' : .<br>
'''ἀναϐολεύς, -έως (nom commun) (m)''' : étrier.<br>
'''ἀναϐολή, -ῆς (nom commun) (f)''' : ajournement, sursis.<br>
'''ἀναγκάζω (verbe)''' : forcer, contraindre.<br>
'''ἀναγκαῖος, -ία, -ῖον (adjectif)''' : (actif) Qui contraint. Nécessaire. Parent pour le sang. (passif) Contraint, forcé.<br>
'''ἀνάγκη, -ης (nom propre) (f)''' : Nécessité, contrainte.<br>
'''ἀναγράφω (verbe)''' : .<br>
'''ἀνάδημα, -τος (nom commun) (n)''' : anadème.<br>
'''ἀνάδοχος, -όχου (nom commun) (m/f)''' : parrain, marraine.<br>
'''ἀναδρομή, -ῆς (nom commun) (f)''' : rétrospective.<br>
'''ἀναδρομικός, -ή, -όν (adjectif)''' : rétrospectif.<br>
'''ἀναδρομικῶς (adverbe)''' : rétrospectivement.<br>
'''ἀνάδυσις, -ύσεως (nom commun) (f)''' : émergence.<br>
'''ἀναδύομαι (verbe)''' : émerger.<br>
'''ἀναζήτησις, -ήσεως (nom commun) (f)''' : recherche.<br>
'''ἀναζητῶ (verbe)''' : rechercher.<br>
'''ἀναθεωρῶ (verbe)''' : réviser.<br>
'''ἀναθυμίασις, -άσεως (nom commun) (f)''' : effluve.<br>
'''ἀναίδεια, -ας (nom commun) (f)''' : impudence.<br>
'''ἀναιδέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἀναιδής''.<br>
'''ἀναιδέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἀναιδής''.<br>
'''ἀναιδής, -ής, -ές (adjectif)''' : impudent.<br>
'''ἀναιδῶς (adverbe)''' : impudemment.<br>
'''ἀναιμία, -ας (nom commun) (f)''' : manque de sang.<br>
'''ἀναισχυντία, -ας (nom commun) (f)''' : effronterie.<br>
'''ἀναίσχυντος, -ή, -όν (adjectif)''' : effronté.<br>
'''ἀναίσχυντότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀναίσχυντός''.<br>
'''ἀναίσχυντότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀναίσχυντός''.<br>
'''ἀναίσχυντότατα, -, - (adverbe)''' : Superlatif de ''ἀναίσχυντῶς''.<br>
'''ἀναίσχυντότερον, -, - (adverbe)''' : Comparatif de ''ἀναίσχυντῶς''.<br>
'''ἀναίσχυντῶς (adverbe)''' : effrontément.<br>
'''ἀνακινῶ (verbe)''' : .<br>
'''ἀνακοίνωσις, -ώσεως (nom commun) (f)''' : communication.<br>
'''ἀνακοινῶ (verbe)''' : communiquer.<br>
'''ἀνακουφίζω (verbe)''' : soulager.<br>
'''ἀνακούφισις, -ίσεως (nom commun) (f)''' : soulagement.<br>
'''ἀνακτορία, -ας (nom commun) (f)''' : .<br>
'''ἀνακτόριος, -ος, -ον (adjectif)''' : .<br>
'''ἀνάκτορον, -όρου (nom commun) (n)''' : .<br>
'''ἀναλαμϐάνω (verbe)''' : reprendre.<br>
'''ἀνάλαψις, -άψεως (nom commun) (f)''' : Forme dorienne de ''ἀνάληψις''.<br>
'''ἀνάληψις, -ήψεως (nom commun) (f)''' : Reprise, reprise de forces, rétablissement. Reconnaissance d'un enfant, action de le faire sien. (Religion chrétienne) Ascension, action d’être repris par le Ciel. Réception.<br>
'''ἀναλογία, -ας (nom commun) (f)''' : proportion.<br>
'''ἀναλογικός, -ή, -όν (adjectif)''' : proportionnel.<br>
'''ἀναλογικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀναλογικός''.<br>
'''ἀναλογικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀναλογικός''.<br>
'''ἀναλογικότατα, -, - (adverbe)''' : Superlatif de ''ἀναλογικῶς''.<br>
'''ἀναλογικότερον, -, - (adverbe)''' : Comparatif de ''ἀναλογικῶς''.<br>
'''ἀναλογικῶς (adverbe)''' : proportionnellement.<br>
'''ἀνάλογος, -η, -ον (adjectif)''' : commensurable.<br>
'''ἀναμένω (verbe)''' : surveiller.<br>
'''ἀνάμνησις, -ήσεως (nom commun) (f)''' : commémoration.<br>
'''ἀναμνηστικός, -ή, -όν (adjectif)''' : commémoratif.<br>
'''ἀναμνηστικῶς (adverbe)''' : commémorativement.<br>
'''ἀναμνηστικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀναμνηστικός''.<br>
'''ἀναμνηστικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀναμνηστικός''.<br>
'''ἀναμνηστικώτατα, -, - (adverbe)''' : Superlatif de ''ἀναμνηστικῶς''.<br>
'''ἀναμνηστικώτερον, -, - (adverbe)''' : Comparatif de ''ἀναμνηστικῶς''.<br>
'''ἄναξις, -εως (nom commun) (f)''' : .<br>
'''ἄναξ, -κτος (nom commun) (m)''' : maître, chef, roi.<br>
'''ἀνάπαιστος, -ίστου (nom commun) (m)''' : anapeste.<br>
'''ἀνάπαυσις, -ύσεως (nom commun) (f)''' : repos.<br>
'''ἀναπαύω (verbe)''' : reposer.<br>
'''ἄνασσα, -ας (nom commun) (f)''' : maîtresse, reine.<br>
'''ἀνάστασις, -άσεως (nom commun) (f)''' : résurrection.<br>
'''ἀναστεναγμός, -οῦ (nom commun) (m)''' : soupir.<br>
'''ἀναστενάζω (verbe)''' : soupirer.<br>
'''ἀνατέλλω (verbe)''' : .<br>
'''ἀνατολή, -ῆς (nom commun) (f)''' : est.<br>
'''ἀνατολικός, -ή, -όν (adjectif)''' : oriental.<br>
'''ἀνατολικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀνατολικός''.<br>
'''ἀνατολικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀνατολικός''.<br>
'''ἀνατολικότατα, -, - (adverbe)''' : Superlatif de ''ἀνατολικῶς''.<br>
'''ἀνατολικότερον, -, - (adverbe)''' : Comparatif de ''ἀνατολικῶς''.<br>
'''ἀνατολικῶς (adverbe)''' : orientalement.<br>
'''ἀναφέρω (verbe)''' : alléguer ; mentionner.<br>
'''ἀναφλέγω (verbe)''' : enflammer.<br>
'''ἀνάφλεξις, -έξεως (nom commun) (f)''' : combustion.<br>
'''ἀναφορά, -ᾶς (nom commun) (f)''' : allégation, mention.<br>
'''ἀναφορικός, -ή, -όν (adjectif)''' : allégatoire.<br>
'''ἀναχώρησις, -ήσεως (nom commun) (f)''' : départ.<br>
'''ἀνδραποδίζω (verbe)''' : vendre des hommes libres en esclavage.<br>
'''ἀνδράποδον, -όδου (nom commun) (n)''' : captif.<br>
'''ἀνδρείκελον, -έλου (nom commun) (n)''' : marionnette.<br>
'''ἀνδρεῖος, -ία, -ῖον (adjectif)''' : masculin.<br>
'''ἀνδρειότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀνδρεῖος''.<br>
'''ἀνδρειότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀνδρεῖος''.<br>
'''ἀνδρείως (adverbe)''' : masculinement.<br>
'''ἀνδρομανία, -ας (nom commun) (f)''' : andromanie.<br>
'''ἀνδροπρέπεια, -ίας (nom commun) (f)''' : virilité.<br>
'''ἀνδροπρεπής -ής -ές (adjectif)''' : viril.<br>
'''ἀνδροπρεπῶς (adverbe)''' : virilement.<br>
'''ἀνδρότης, -τος (nom commun) (f)''' : virilité.<br>
'''ἀνδρών, -ῶνος (nom commun) (m)''' : Appartement réservé aux hommes.<br>
'''ἀνεκδιήγητος, -ος, -ον (adjectif)''' : inénarrable.<br>
'''ἀνέκδοτος, -ος, -ον (adjectif)''' : Non donnée en mariage, en parlant d’une jeune fille. Inédit, non publié.<br>
'''ἀνέμη, -ης (nom commun) (f)''' : rouet.<br>
'''ἄνεμος, -έμου (nom commun) (m)''' : vent.<br>
'''ἀνερρίπτω (verbe)''' : .<br>
'''ἀνερωτῶ (verbe)''' : .<br>
'''ἀνευθυνότης, -ητος (nom commun) (f)''' : irresponsabilité.<br>
'''ἀνευθύνω (verbe)''' : être irresponsable.<br>
'''ἀνεύρυσμα, -ύσματος (nom commun) (n)''' : élargissement, dilatation.<br>
'''ἀνευρύνω (verbe)''' : élargir, dilater.<br>
'''ἀνεψιά, -ᾶς (nom commun) (f)''' : nièce.<br>
'''ἀνεψιός, -οῦ (nom commun) (m)''' : neveu.<br>
'''ἄνηθον, -ήθου (nom commun) (n)''' : aneth.<br>
'''ἀνήρ, -δρός (nom commun) (n)''' : Homme, époux ; mâle des animaux.<br>
'''ἄνθεμον, -έμου (nom commun) (n)''' : Diminutif d’''ἄνθος''.<br>
'''ἀνθολογέω (verbe)''' : cueillir des fleurs.<br>
'''ἁνθολογία, -ας (nom commun) (f)''' : florilège.<br>
'''ἀνθολόγιον, -ου (nom commun) (n)''' : recueil.<br>
'''ἀνθόλωψ, -πος (nom commun) (m)''' : antilope.<br>
'''ἄνθος, -ους (nom commun) (n)''' : fleur.<br>
'''ἄνθραξ, κος (nom commun) (m)''' : charbon.<br>
'''ἀνθρακιά, -ᾶς (nom commun) (f)''' : pile de charbon.<br>
'''ἀνθρωπάριον, -ίου (nom commun) (n)''' : homuncule.<br>
'''ἀνθρώπινος, -η, -ον (adjectif)''' : humain.<br>
'''ἀνθρωπίσκος, -ου (nom commun) (m)''' : mannequin.<br>
'''ἄνθρωπος, -ώπου (nom commun) (m)''' : homme, genre humain.<br>
'''ἀνθρωποκεντρικός, -ή, -όν (adjectif)''' : anthropocentrique.<br>
'''ἀνθρωποειδής, -ής, -ες, (adjectif)''' : anthropoïde.<br>
'''ἀνθρωπολογία, -ας''' : anthropologie.<br>
'''ἀνθρωπομορφισμός, -οῦ (nom commun) (m)''' : anthropomorphisme.<br>
'''ἀνθρωποφαγία, -ας (nom commun) (f)''' : cannibalisme.<br>
'''ἀνθρωποφάγος, -ου (nom commun) (n)''' : cannibale.<br>
'''ἀνία, -ας (nom commun) (f)''' : ennui.<br>
'''ἁνία, -ας (nom commun) (f)''' : Forme dorienne de ''ἡνία''.<br>
'''ἄνισος, -η, -ον (adjectif)''' : inégal.<br>
'''ἀνίστημι (verbe)''' : ressusciter.<br>
'''ἄν (adverbe, particule)''' : .<br>
'''ἄννησον, -ήσου (nom commun) (n)''' : anis.<br>
'''ἀνόδων, -οντος (nom commun) (m/f)''' : édenté.<br>
'''ἀνόητος, -ος, -ον (adjectif)''' : .<br>
'''ἄνοια, -ας (nom commun) (f)''' : démence.<br>
'''ἄνοιγμα, -ίγματος (nom commun) (n)''' : orifice, ouverture.<br>
'''ἀνοίγω (verbe)''' : ouvrir.<br>
'''ἄνοιξις, -ίξεως (nom commun) (f)''' : ouverture.<br>
'''ἀνωμαλία, -ας (nom commun) (f)''' : inégalité, irrégularité.<br>
'''ἀνώμαλος, -ος, -ον (adjectif)''' : inégal, irrégulier.<br>
'''ἄνοικτος, -ος, -ον (adjectif)''' : impitoyable.<br>
'''ἀνοικτότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἄνοικτος''.<br>
'''ἀνοικτότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἄνοικτος''.<br>
'''ἀνοικτότατα, -, - (adverbe)''' : Superlatif de ''ἀνοίκτως''.<br>
'''ἀνοικτότερον, -, - (adverbe)''' : Comparatif de ''ἀνοίκτως''.<br>
'''ἀνοίκτως (adverbe)''' : impitoyablement.<br>
'''ἀνορεξία, -ας (nom commun) (f)''' : manque d’appétit.<br>
'''ἄνους, -ους, -ουν (adjectif)''' : dément.<br>
'''ἀνταγωνίζεσθαι (verbe)''' : contrarier.<br>
'''ἀνταγωνιστής, -οῦ (nom commun) (m)''' : adversaire.<br>
'''ἀνταίρω (verbe)''' : se rebeller.<br>
'''ἀνταπαίτησις, -ήσεως (nom commun) (f)''' : .<br>
'''ἀνταπαιτητής, -οῦ (nom commun) (m)''' : .<br>
'''ἀνταπαιτῶ (verbe)''' : .<br>
'''ἀνταρσία, -ας (nom commun) (m)''' : rébellion.<br>
'''ἀντάρτης, -ου (nom commun) (m)''' : rebelle.<br>
'''ἀντάρτικος, -η, -ον (adjectif)''' : rebelle.<br>
'''ἀντάρτικῶς (adverbe)''' : Forme adverbiale de ''ἀντάρτικός''.<br>
'''ἀντάρτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀντάρτικός''.<br>
'''ἀντάρτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀντάρτικός''.<br>
'''ἀντάρτικώτατα, -, - (adverbe)''' : Superlatif de ''ἀντάρτικῶς''.<br>
'''ἀντάρτικώτερον, -, - (adverbe)''' : Comparatif de ''ἀντάρτικῶς''.<br>
'''ἀντέχω (verbe)''' : endurer.<br>
'''ἀντιάς, -δος (nom commun) (f)''' : (anatomie) amygdale.<br>
'''ἀντιγραφέυς, -έως (nom commun) (m)''' : copiste.<br>
'''ἀντιγραφή, -ῆς (nom commun) (f)''' : recopiage.<br>
'''ἀντιγράφω (verbe)''' : copier. (un texte écrit)<br>
'''ἀντί (préposition)''' : En face de. À l’encontre de, contre. Au lieu de, à la place de. À l’égal de. En échange de.
Par succession, par addition. En comparaison de. (En mot composé) En face, à l'encontre. En opposition avec. En échange de, en retour. Au lieu de, à l'égal de. Par correspondance. (En composition) En face, à l'encontre.<br>
'''ἀντίληψις, -ήψεως (nom commun) (f)''' : perception.<br>
'''ἀντιπαράθεσις, -έσεως (nom commun) (f)''' : confrontation.<br>
'''ἀντιπαρατίθημι (verbe)''' : confronter.<br>
'''ἀντίπους, -δός (nom commun) (m)''' : antipode.<br>
'''ἀντίρρησις, -ήσεως (nom commun) (f)''' : objection.<br>
'''ἀντίστοιχος, -ος, -ον (adjectif)''' : correspondant.<br>
'''ἀντιστοίχοτατα, -, - (adverbe)''' : Superlatif de ''ἀντιστοίχως''.<br>
'''ἀντιστοίχοτερον, -, - (adverbe)''' : Comparatif de ''ἀντιστοίχως''.<br>
'''ἀντιστοίχως (adverbe)''' : conformément.<br>
'''ἀντιστοιχῶ (verbe)''' : correspondre.<br>
'''ἀντισυνταγματικός, -ή, -όν (adjectif)''' : inconstitutionnel.<br>
'''ἀντισυνταγματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀντισυνταγματικός''.<br>
'''ἀντισυνταγματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀντισυνταγματικός''.<br>
'''ἀντισυνταγματικώτατα, -, - (adverbe)''' : Superlatif de ''ἀντισυνταγματικῶς''.<br>
'''ἀντισυνταγματικώτερον, -, - (adverbe)''' : Comparatif de ''ἀντισυνταγματικῶς''.<br>
'''ἀντισυνταγματικότης, -τος (nom commun) (f)''' : inconstitutionnalité.<br>
'''ἀντισυνταγματικῶς (adverbe)''' : inconstitutionnellement.<br>
'''ἀντίφασις, -άσεως (nom commun) (f)''' : contradiction.<br>
'''ἀντιφάσκω (verbe)''' : contredire.<br>
'''ἀντιφατικός -ή -όν (adjectif)''' : contradictoire.<br>
'''ἀντιφατικῶς (adverbe)''' : contradictoirement.<br>
'''ἀντιφατικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀντισυνταγματικός''.<br>
'''ἀντιφατικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀντισυνταγματικός''.<br>
'''ἀντιφατικώτατα, -, - (adverbe)''' : Superlatif de ''ἀντιφατικῶς''.<br>
'''ἀντιφατικώτερον, -, - (adverbe)''' : Comparatif de ''ἀντιφατικῶς''.<br>
'''ἀντιφατικότης, -τος (nom commun) (f)''' : contradiction.<br>
'''ἀντίχειρ, -ος (nom commun) (m)''' : pouce.<br>
'''ἀντοχή, -ῆς (nom commun) (f)''' : endurance.<br>
'''ἀνυπακοή, -ῆς (nom commun) (f)''' : désobéissance.<br>
'''ἀνυπάκουος, -η, -ον (adjectif)''' : désobéissant.<br>
'''ἀνύψωσις, -ώσεως (nom commun) (f)''' : élévation.<br>
'''ἀνυπακούω (verbe)''' : désobéir.<br>
'''ἀνώδυνος, -ος, -ον (adjectif) ''' : qui n’est pas douloureux. Qui n’est pas causé par la douleur.<br>
'''ἀνώγειον, -ίου (nom commun) (n)''' : grenier.<br>
'''ἀνώτατος, -άτη, -ώτατον (adjectif)''' : suprême.<br>
'''ἀνώτερος, -έρα, -ώτερον (adjectif)''' : supérieur.<br>
'''ἀνωτερότης, -τος (nom commun) (f)''' : supériorité.<br>
'''ἄνω (adverbe)''' : Sur, vers le haut ; au dessus.<br>
'''ἀξιάκουστος, -ος, -ον (adjectif)''' : digne d’être écouté.<br>
'''ἀξίνη, -ης (nom commun) (f)''' : pioche (outil). Hache<br>
'''ἀξιο- (préfixe)''' : qui est digne de.<br>
'''ἀξιοθέατος, -ος, -ον (adjectif)''' : digne d’être contemplé.<br>
'''ἀξιόπιστος, -ος, -ον (adjectif)''' : digne de foi.<br>
'''ἄξιος, -α, -ον (adjectif)''' : de valeur, digne de, méritant.<br>
'''ἀξίωμα, -τος (nom commun) (n)''' : Prix, valeur. Ce dont on a été jugé digne. Considération, estime. Marque de considération, honneur. Haut rang, dignité. Ce que l’on juge convenable, ce qui paraît juste.<br>
'''ἀξιῶ (verbe)''' : mériter (quelque chose).<br>
'''ἄξων, -ονος (nom commun) (m)''' : Axe. Essieu de roue. Axe du ciel, du monde. Axe d’un chemin d’où chemin, route. Crochet du mors d’un cheval. Tablette de bois construite sur un pivot. Arbre ou axe de rotation, pivot, battant balistique.<br>
'''ἀοιδός, -οῦ (nom commun) (m)''' : Chanteur ; chantre.<br>
'''ἀόρατος, -ος, ον (adjectif)''' : invisible.<br>
'''ἄορ, -ος (nom commun) (n)''' : épée pendue à la ceinture.<br>
'''ἀορτήρ, -ῆρος (nom commun) (m)''' : bandoulière.<br>
'''ἀπαγορευτικός, -ή -όν (adjectif)''' : prohibitif.<br>
'''ἀπαγόρευσις, -ύσεως (nom commun) (f)''' : prohibition.<br>
'''ἀπαγορεύω (verbe)''' : prohiber.<br>
'''ἀπαγωγεύς, -έως (nom commun) (m)''' : ravisseur.<br>
'''ἀπαγωγή, -ῆς (nom commun) (f)''' : (Droit) En termes de droit athénien, action d’emmener à un procès un malfaiteur pris en flagrant délit. Action de faire dévier du droit chemin. Paiement (d’une contribution ou d’une amende).<br>
'''ἀπάγω (verbe)''' : conduire ; emmener.<br>
'''ἀπαισιοδοξία, -ας (nom commun) (f)''' : pessimisme.<br>
'''ἀπαίτησις, -ήσεως (nom commun) (f)''' : exigence (ce que l’on exige).<br>
'''ἀπαιτῶ (verbe)''' : exiger.<br>
'''ἀπαλλαγή, -ῆς (nom commun) (f)''' : exonération.<br>
'''ἀπαλλακτικός, -ή -όν (adjectif)''' : exonérant.<br>
'''ἀπαλλάσσω (verbe)''' : exonérer.<br>
'''ἀπαπαῖ (interjection)''' : ouille.<br>
'''ἀπαρνοῦμαι (verbe)''' : .<br>
'''ἀπάτωρ, -ωρ, -ορ (adjectif)''' : sans père.<br>
'''ἄπειμι (verbe)''' : Être absent, s’absenter. Partir, s’en aller.<br>
'''ἀπειρέσιος, -ία, -έσιον (adjectif)''' : Illimité, immense ; innombrable.<br>
'''ἀπειρεσιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀπειρέσιος''.<br>
'''ἀπειρεσιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀπειρέσιος''.<br>
'''ἀπειρεσίως (adverbe)''' : immensément.<br>
'''ἄπειρος, -ος, -ον (adjectif)''' : infini.<br>
'''ἀπειρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἄπειρος''.<br>
'''ἀπειρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἄπειρος''.<br>
'''ἀπείρως (adverbe)''' : infiniment.<br>
'''ἀπελευθέρωσις, -ώσεως (nom commun) (f)''' : libération.<br>
'''ἀπεργάζομαι (verbe)''' : .<br>
'''ἀπέχθεια, -ας (nom commun) (f)''' : répugnance.<br>
'''ἀπεχθής, -ής, -ές (adjectif)''' : répugnant.<br>
'''ἀπήνη, -ης (nom commun) (f)''' : .<br>
'''ἀπίθανος, -ος, -ον (adjectif)''' : improbable ; incroyable.<br>
'''ἄπιον, -ίου (nom commun) (n)''' : poire.<br>
'''ἀπιστία, -ας (nom commun) (f)''' : infidélité ; déloyauté.<br>
'''ἀπιστίη, -ης (nom commun) (f)''' : Forme ionienne de ''ἀπιστία''.<br>
'''ἄπιστος, -ος, -ον (adjectif)''' : infidèle ; déloyal.<br>
'''ἀπλάνεια, -ίας (nom commun) (f)''' : constance.<br>
'''ἄπληστος, -η, -ον (adjectif)''' : .<br>
'''ἁπλότης, -τος (nom commun) (f)''' : simplicité.<br>
'''ἁπλοῦς, -ῆ, -οῦν (adjectif)''' : simple.<br>
'''ἀπό (adverbe ; préposition)''' (Devient ''ἀπ’'' devant un mot commençant par une voyelle à esprit doux, et ''ἀφ’'' devant un mot commençant par une voyelle à esprit rude.) : Au loin, en venant de.<br>
'''ἀπό- (préfixe)''' : séparation, éloignement, changement, achèvement, cessation, retour, privation, négation.<br>
'''ἀπόγειον, -ίου (nom commun) (n)''' : apogée.<br>
'''ἀπόγειος, -ος, -ον (adjectif)''' : Qui part de terre, ou vient de son souffle. Éloigné de la terre.<br>
'''ἀπόγονος, -όνου (nom commun) (m)''' : descendant.<br>
'''ἀπογραφή, -ῆς (nom commun) (f)''' : Registre, liste. Copie.<br>
'''ἀπογράφω (m)''' : copier.<br>
'''ἀπόδειξις, -ίξεως (nom commun) (f)''' : évidence, preuve.<br>
'''ἀποδεικτικός, -ή, -όν (adjectif)''' : évident.<br>
'''ἀποδεικτικῶς (adverbe)''' : évidemment.<br>
'''ἀποδεικτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀποδεικτικός''.<br>
'''ἀποδεικτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀποδεικτικός''.<br>
'''ἀποδεικτικώτατα, -, - (adverbe)''' : Superlatif de ''ἀποδεικτικῶς''.<br>
'''ἀποδεικτικώτερον, -, - (adverbe)''' : Comparatif de ''ἀποδεικτικῶς''.<br>
'''ἀπολογητικός, -ή, -όν (adjectif)''' : défensif.<br>
'''ἀπολογητικῶς (adverbe)''' : défensivement.<br>
'''ἀπολογητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀπολογητικός''.<br>
'''ἀπολογητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀπολογητικός''.<br>
'''ἀπολογητικώτατα, -, - (adverbe)''' : Superlatif de ''ἀπολογητικῶς''.<br>
'''ἀπολογητικώτερον, -, - (adverbe)''' : Comparatif de ''ἀπολογητικῶς''.<br>
'''ἀπολογία, -ας (nom commun) (f)''' : Défense, justification.<br>
'''ἀπόλυτος, -η, -ον (adjectif)''' : absolu.<br>
'''ἀπολύτως (adverbe)''' : absolument.<br>
'''ἀπολύω (verbe)''' : congédier, licencier ; renvoyer.<br>
'''ἀποπομπαῖος, -ία, -ῖον (adjectif)''' : expiatoire.<br>
'''ἀποπέμπω (verbe)''' : emporter le mal.<br>
'''ἀποθνῄσκω (verbe)''' : mourir.<br>
'''ἀποκάλυψις, -ύψεως (nom commun) (f)''' : dévoilement.<br>
'''ἀποκαλύπτω (verbe)''' : dévoiler.<br>
'''ἀποκεφαλισμός, -οῦ (nom commun) (f)''' : décapitation.<br>
'''ἀποκεφαλίζω (verbe)''' : décapiter.<br>
'''ἀποκρουστικός, -ή, -όν (adjectif)''' : . <br>
'''ἀποκρούω (verbe)''' : . <br>
'''ἀπόκρυφος, -ος, -ον (adjectif)''' : . <br>
'''ἀπολαμϐάνω (verbe)''' : jouir. (Avoir l’usage, la possession actuelle de quelque chose.)<br>
'''ἀπόλαυσις, -ύσεως (nom commun) (f)''' : jouissance (Satisfaction voluptueuse ; plaisir né de la relation sexuelle épanouie.)<br>
'''ἀπολαύω (verbe)''' : jouir. (Éprouver un vif plaisir, un orgasme, etc.)<br>
'''ἀπολέγω (verbe)''' : Décliner, refuser.<br>
'''ἀπολογία, -ας (nom commun) (f)''' : Défense, justification.<br>
'''ἀπόλογος, -όγου (nom commun) (m)''' : Défense. Narration, récit détaillé.<br>
'''ἀποπατῶ (verbe)''' : déféquer.<br>
'''ἀποπνίγω (verbe)''' : suffoquer.<br>
'''ἀπόρρηγμα, -ήγματος (nom commun) (n)''' : fragment.<br>
'''ἀπορρίπτω (verbe)''' : .<br>
'''ἀπόσπασμα, -άσματος (nom commun) (n)''' : fragment.<br>
'''ἀποστασία, -ας (nom commun) (f)''' : Révolte, défection ; départ.<br>
'''ἀπόστασις, -άσεως (nom commun) (f)''' : éloignement.<br>
'''ἀφίστημι (verbe)''' : Rejeter, se retirer ; se révolter.<br>
'''ἀποστρέφω (verbe)''' : .<br>
'''ἀποστροφή, -ῆς (nom commun) (f)''' : répulsion.<br>
'''ἀπόστροφος, -ος, -ον (adjectif)''' : répulsif.<br>
'''ἀποτέλεσμα, -έσματος (nom commun) (n)''' : résultat ; effet.<br>
'''ἀποτελῶ (verbe)''' : échouer.<br>
'''ἀποστέρησις, -ήσεως (nom commun) (f)''' : frustration.<br>
'''ἀποστερῶ (verbe)''' : frustrer.<br>
'''ἀποτέφρωσις, -ώσεως (nom commun) (f)''' : incinération.<br>
'''ἀποτεφρῶ (verbe)''' : incinérer.<br>
'''ἀποτυγχάνω (verbe)''' : échouer.<br>
'''ἀποτυχία, -ας (nom commun) (f)''' : échec.<br>
'''ἀπουσία, -ας (nom commun) (f)''' : absence.<br>
'''ἄπους, -ους, -ουν (adjectif)''' : apode.<br>
'''ἀποφαίνω (verbe)''' : décider.<br>
'''ἀπόφασις, -άσεως (nom commun) (f)''' : décision.<br>
'''ἀποφεύγω (verbe)''' : s’échapper.<br>
'''ἀπόφημι (verbe)''' : .<br>
'''ἀποφθέγγομαι (verbe)''' : .<br>
'''ἀπόφθεγμα, -έγματος (nom commun) (n)''' : précepte, sentence.<br>
'''ἀποφθορά, -ᾶς (nom commun) (f)''' : avortement.<br>
'''ἀποφόρητον, -ου (nom commun) (n)''' : étrenne.<br>
'''ἀποφυγή, -ῆς (nom commun) (f)''' : échappée.<br>
'''ἄποψις, -όψεως (nom commun) (f)''' : opinion, point de vue.<br>
'''ἀποψύχω (verbe)''' : s’évanouir.<br>
'''ἁπτικός, -ή, -όν (adjectif)''' : tactile.<br>
'''ἀπύ (adverbe ; préposition)''' : Forme arcado-chypriote et éolienne de ''ἀπό''.<br>
'''ἀπών, -οῦσα, -όν (adjectif)''' : absent.<br>
'''ἅπτω (verbe)''' : toucher.<br>
'''ἅπτω (verbe)''' : ajuster, attacher ; nouer.<br>
'''ἄρα (conjonction)''' : (Devient ''ἄρ’'' devant un mot commençant par une voyelle, et ''ῥά'' après un mot monosyllabique ou un mot finissant par une voyelle.) puis, et, alors. Par suite, ainsi donc, donc. Puisque, à savoir, c’est-à-dire, en effet. Ayant donc, ainsi parlé.<br>
'''ἆρα (particule)''' : (Devient ''ἆρ’'' devant un mot commençant par une voyelle.) est-ce que.<br>
'''ἀρά, -ᾶς (nom commun) (f)''' : Prière. Imprécation.<br>
'''ἄρακος, -άκου (nom commun) (m)''' : pois.<br>
'''ἀράχνη, -ης (nom commun) (f)''' : araignée.<br>
'''ἀρϐύλη, -ης (nom commun) (f)''' : botte (chaussure épaisse au long col).<br>
'''ἄρδις, -ος (nom commun) (f)''' : pointe de flèche.<br>
'''ἀργά (adverbe)''' : tard.<br>
'''ἄργιλλος, -ίλλου (nom commun) (m)''' : argile.<br>
'''ἀργιλλοφόρητος''' : mot fantôme selon Rosane Rocher (1961), comme καλποφόρος, ὀφιοφόρος, ὠποφόρος et σμνρμοφόρος.<br>
'''ἀργιλλώδης (adjectif)''' : argileux.<br>
'''ἀργός, -ή, -όν (adjectif)''' : blanc ; étincelant.<br>
'''ἄργυρος, -ύρου (nom commun) (m)''' : argent. (métal)<br>
'''ἀργῶ (verbe)''' : .<br>
'''ἀρετά, -ᾶς (nom commun) (f)''' : Forme dorienne de ''ἀρετή''.<br>
'''ἀρετή, -ῆς (nom commun) (f)''' : vertu.<br>
'''ἀρή, -ῆς (nom commun) (f)''' : Forme ionienne de ''ἀρά''.<br>
'''ἄρθρον, -ου (nom commun) (n)''' : Jointure, articulation. Article. (outil grammatical)<br>
'''ἀριστερός, -ά, -όν (adjectif)''' : Qui est à gauche.<br>
'''ἀριστόϐουλος, -ος, -ον (adjectif)''' : .<br>
'''ἄριστος, -η, -ον (adjectif)''' : excellent.<br>
'''ἀρκαδικός, -ή, -όν (adjectif)''' : arcadien.<br>
'''ἄρκευθος, -ύθου (nom commun) (f)''' : genévrier.<br>
'''ἄρκτος, -ου (nom commun) (m/f)''' : ours(e).<br>
'''ἄρκυς, -ος (nom commun) (m)''' : filet.<br>
'''ἄρμα, -τος (nom commun) (t)''' : char.<br>
'''ἁρμονία, -ας (nom commun) (f)''' : harmonie.<br>
'''ἁρμονικός, -ή, -όν (adjectif)''' : harmonieux.<br>
'''ἁρμονικῶς (adverbe)''' : harmonieusement.<br>
'''ἁρμονικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἁρμονικός''.<br>
'''ἁρμονικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἁρμονικός''.<br>
'''ἁρμονικώτατα, -, - (adverbe)''' : Superlatif de ''ἁρμονικῶς''.<br>
'''ἁρμονικώτερον, -, - (adverbe)''' : Comparatif de ''ἁρμονικῶς''.<br>
'''ἀρνοῦμαι (verbe)''' : refuser.<br>
'''ἄρουρα, -ας (nom commun) (f)''' : aroure.<br>
'''ἀρουραῖος, -ίου (nom commun) (m)''' : rat.<br>
'''ἁρπακτικός, -οῦ (nom commun) (m)''' : prédateur.<br>
'''ἅρπαξ, -γος (nom commun) (m)''' : rapace, pillard.<br>
'''ἁρπίς, -ῖδος (nom commun) (f)''' : pantoufle.<br>
'''ἀρραϐών, -ῶνος (nom commun) (n)''' : arrhes.<br>
'''ἄρρητος, -ος, -ον (adjectif)''' : Indicible ; ineffable, (Mathématiques) irrationnel.<br>
'''ἄρσην, -ην, -εν (adjectif)''' : Mâle ; dur, fort.<br>
'''ἄρρην, -ενος (nom commun) (m)''' : Forme attique de ''ἄρσην''.<br>
'''ἄρσην, -ενος (nom commun) (m)''' : Homme adulte ; mâle.<br>
'''ἄρσης, -ενος (nom commun) (m)''' : Forme laconienne de ''ἄρσην''.<br>
'''ἄρριχος, -ίχου (nom commun) (m)''' : panier.<br>
'''ἄρρωστος, -ος, -ον (adjectif)''' : .<br>
'''ἀρρωστότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἄρρωστος''.<br>
'''ἀρρωστότερος, -έρα, -ότερον (adjectif)'''' : Comparatif de ''ἄρρωστος''.<br>
'''ἀρρωστότατα, -, - (adverbe)''' : Superlatif de ''ἀρρώστως''.<br>
'''ἀρρωστότερον, -, - (adverbe)''' : Comparatif de ''ἀρρώστως''.<br>
'''ἀρρώστως (adverbe)''' : .<br>
'''ἄρτι (adverbe)''' : justement ; exactement. Maintenant.<br>
'''ἄρτιος, -ία, -ον (adjectif)''' : Parfait, complet ; achevé. (Mathématiques) Pair, en parlant des nombres.<br>
'''ἀρτίως (adverbe)''' : parfaitement ; complètement.<br>
'''ἀρτιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἄρτιος''.<br>
'''ἀρτιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἄρτιος''.<br>
'''ἀρτοποιός, -οῦ (nom commun) (m)''' : boulanger .<br>
'''ἄρτος, -ου (nom commun) (m)''' : pain. (aliment)<br>
'''ἀρτῶ (verbe)''' : Attacher. (Au passif) Dépendre, pendre, être attaché à.<br>
'''ἀρύϐαλλος, ου (nom commun) (m)''' : flacon à huile.<br>
'''ἀρχάγγελος, -έλου (nom commun) (m)''' : messager en chef ; archange.<br>
'''ἀρχαῖος, -ία, -ῖον (adjectif)''' : ancien.<br>
'''ἀρχαιότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀρχαῖος''.<br>
'''ἀρχαιότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀρχαῖος''.<br>
'''ἀρχαίως (adverbe)''' : anciennement.<br>
''', -, - (adverbe)''' : Superlatif de ''ἀρχαίως''.<br>
''', -, - (adverbe)''' : Comparatif de ''ἀρχαίως''.<br>
'''ἀρχαϊκός, -ή, -όν (adjectif)''' : vieilli.<br>
'''ἀρχαϊκότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἀρχαϊκός''.<br>
'''ἀρχαϊκότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἀρχαϊκός''.<br>
'''ἀρχαϊκῶς (adverbe)''' : vieillement.<br>
'''ἀρχαϊκώτατα, -, - (adverbe)''' : Superlatif de ''ἀρχαϊκῶς''.<br>
'''ἀρχαϊκώτερον, -, - (adverbe)''' : Comparatif de ''ἀρχαϊκῶς''.<br>
'''ἀρχέτυπον, -ύπου (nom commun) (n)''' : modèle.<br>
'''ἀρχή, -ῆς (nom commun) (f)''' : commencement, commandement.<br>
'''ἀρχηγός, -οῦ (nom commun) (m)''' : chef.<br>
'''-άρχης, -ου (suffixe)''' : qui est chef de.<br>
'''ἀρχι- (préfixe)''' : relatif au commencement, au commandement.<br>
'''ἀρχιδιάκονος, -όνου (nom commun) (m/f)''' : archidiacre.<br>
'''ἀρχιδιήκονος, -όνου (nom commun) (m/f)''' : Forme ionienne de ''ἀρχιδιάκονος''.<br>
'''ἀρχίκλωψ, -πός (nom commun) (m)''' : maître voleur.<br>
'''ἀρχίμιμος, -ίμου (nom commun) (m)''' : archimime.<br>
'''ἀρχιτέκτων, -ονος (nom commun) (m)''' : maître d’œuvre.<br>
'''ἄρχων, -οντος (nom commun) (m)''' : archonte.<br>
'''ἄρωμα, -ώματος (nom commun) (n)''' : parfum.<br>
'''ἄρω (verbe)''' : nouer.<br>
'''ἄσϐολος, -όλου (nom commun) (f)''' : suie.<br>
'''ἀσέϐεια, -ας (nom commun) (f)''' : impiété.<br>
'''ἀσεϐής, -ής, -ές (adjectif)''' : impie.<br>
'''ἀσεϐῶ (verbe)''' : être impie.<br>
'''ἀσέλγεια, -ας (nom commun) (f)''' : lubricité.<br>
'''ἀσελγέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἀσελγής''.<br>
'''ἀσελγέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἀσελγής''.<br>
'''ἀσελγής, -ής, -ές (adjectif)''' : lubrique.<br>
'''ἀσελγότατα, -, - (adverbe)''' : Superlatif de ''ἀσελγῶς''.<br>
'''ἀσελγότερον, -, - (adverbe)''' : Comparatif de ''ἀσελγῶς''.<br>
'''ἀσελγῶς (adverbe)''' : lubriquement.<br>
'''ἀσελγῶ (verbe)''' : être lubrique.<br>
'''ἀσθενής, -ής, -ές (adjectif)''' : malade.<br>
'''ἄσθμα, -τος (nom commun) (n)''' : asthme.<br>
'''ἄσις, - (nom commun) (f)''' : Boue, argile.<br>
'''ἀσκάλαϐος, -άϐου (nom commun) (m)''' : gecko.<br>
'''ἄσκαυλος, -ύλου (nom commun) (m)''' : cornemuse.<br>
'''ἀσκέω (verbe)''' : entraîner.<br>
'''ἄσκημα, -ήματος (nom commun) (n)''' : entraînement.<br>
'''ἄσκησις, -ήσεως (nom commun) (f)''' : exercice.<br>
'''ἀσκητεία, -ας (nom commun) (f)''' : ascétie.<br>
'''ἀσκητήριον, -ίου (nom commun) (n)''' : .<br>
'''ἀσκητής, -οῦ (nom commun) (m)''' : ascète.<br>
'''ἀσκητικός, -ή, -όν (adjectif)''' : ascétique.<br>
'''ἀσκός, -οῦ (nom commun) (m)''' : outre.<br>
'''ἀσκῶ (verbe)''' : exercer.<br>
'''ᾆσμα, -τος (nom commun) (n)''' : chant.<br>
'''ἀσπάλαξ, -κος (nom commun) (m)''' : taupe.<br>
'''ἄσπαλος, -άλου (nom commun) (m)''' : squale.<br>
'''ἀσπίς, -δος (nom commun) (f)''' : bouclier.<br>
'''ἀστακός, -οῦ (nom commun) (m)''' : homard.<br>
'''ἀστεῖος, -ία, -ῖον (adjectif)''' : urbain.<br>
'''ἀστήρ, -έρος (nom commun) (m)''' : étoile.<br>
'''ἀστικός, -ή, -όν (adjectif)''' : urbain.<br>
'''ἀστραπή, -ῆς (nom commun) (f)''' : éclair.<br>
'''ἄστρον, -ου (nom commun) (n)''' : Astre, constellation, système d’étoiles.<br>
'''ἀστυνομία, -ας (nom commun) (f)''' : police urbaine.<br>
'''ἀστυνόμος, -ου (nom commun) (m)''' : astynome.<br>
'''ἀστύνομος, -ος, -ον (adjectif)''' : public.<br>
'''ἄστυ, -εως (nom commun) (n)''' : Cité ; ville.<br>
'''ἀστυφύλαξ, -κος (nom commun) (m)''' : policier.<br>
'''ἄσυλον, -ύλου (nom commun) (n)''' : asile.<br>
'''ἄσυλος, -ος, -ον (adjectif)''' : inviolable.<br>
'''ἀσυμμετρία, -ας (nom commun) (f)''' : mauvaise proportion.<br>
'''ἄσυχος, -ος, -ον (adjectif)''' : Forme dorienne de ''ἥσυχος''.<br>
'''ἀσφάλεια, -ίας (nom commun) (f)''' : sécurité.<br>
'''ἀσφαλής, -ής, -ές (adjectif)''' : sûr.<br>
'''ἄσφαλτος, -άλτου (nom commun) (f)''' : asphalte.<br>
'''ἀσφαλῶς (adverbe)''' : sûrement.<br>
'''-άς, -δος (suffixe) (f)''' : Forme des noms d’agent.<br>
'''ἄτα, -ας (nom commun)''' : Forme dorienne de ''ἄτη''.<br>
'''ἄτακτος, -η, -ον (adjectif)''' : vilain.<br>
'''ἀταξία, -ας (nom commun) (f)''' : vilénie.<br>
'''ἀταραξία, -ας (nom commun) (f)''' : Calme ; imperturbabilité.<br>
'''ἅτερος, -έρα, -ερον (adjectif)''' : Forme dorienne de ''ἕτερος''.<br>
'''ἄτερος, -έρα, -ερον (adjectif)''' : Forme éolienne de ''ἕτερος''.<br>
'''ἄτη, -ης (nom commun)''' : Outrage. (Religion) Péché, faute. Ruine.<br>
'''ἀτιμάζω (verbe)''' : déshonorer.<br>
'''ἄτρακτος, -άκτου (nom commun) (n)''' : fuseau.<br>
'''ἀτραπός, -οῦ (nom commun) (f)''' : sentier.<br>
'''ἄττα, -ου (nom commun) (m)''' : Forme homérique de ''πάππας'' et ''τατᾶ''.<br>
'''ἀτύχημα, -ήματος (nom commun) (n)''' : accident.<br>
'''ἀτυχῶ (verbe)''' : avoir un accident.<br>
'''αὐθάδεια, -ας (nom commun) (f)''' : insolence.<br>
'''αὐθάδης, -ής, -ές (adjectif)''' : insolent.<br>
'''αὖθις (adverbe)''' : à nouveau.<br>
'''αὖλαξ, -ὔλακος (nom commun) (m)''' : irrigation.<br>
'''αὐλή, -ῆς (nom commun) (f)''' : cour.<br>
'''αὐλητής, -οῦ (nom commun) (m)''' : joueur d’aulos.<br>
'''αὐλητρίς, -δος (nom commun) (f)''' : joueuse d’aulos.<br>
'''αὐλίζομαι (verbe)''' : passer la nuit.<br>
'''αὖλις, - (nom commun) (f)''' : résidence, camp.<br>
'''αὐλός, -οῦ (nom commun) (m)''' : aulos.<br>
'''αὐλῶ (verbe)''' : jouer de l’aulos.<br>
'''αὐξάνω (verbe)''' : augmenter.<br>
'''αὔξη, -ης (nom commun) (f)''' : croissance.<br>
'''αὔξημα, -ήματος (nom commun) (n)''' : .<br>
'''αὔξησις, -ήσεως (nom commun) (f)''' : Accroissement, hausse.<br>
'''αὔξιμος, -ος, -ον (adjectif)''' : .<br>
'''αὐξητικός, -ή, -όν (adjectif)''' : .<br>
'''αὔξω (verbe)''' : croître.<br>
'''ἀϋπνία, -ας (nom commun) (f)''' : insomnie.<br>
'''αὔρα, -ας (nom commun) (f)''' : aura.<br>
'''αὔρη, -ης (nom commun) (f)''' : Forme ionienne de ''αὔρα''.<br>
'''αὔριον (adverbe)''' : demain, bientôt.<br>
'''αὐστηρός, -ή, -όν (adjectif)''' : sévère.<br>
'''αὐστηρότης, -τος (nom commun) (f)''' : sévérité.<br>
'''αὐστηρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''αὐστηρότατος''.<br>
'''αὐστηρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''αὐστηρότατος''.<br>
'''αὐστηρῶς (adverbe)''' : sévèrement.<br>
'''αὐτανάφλεξις, -έξεως (nom commun) (f)''' : combustion spontanée.<br>
'''αὐτάναξ, -κτος (nom commun) (m)''' : empereur.<br>
'''αὐτάρ (adverbe)''' : mais, cependant.<br>
'''αὐτίκα (adverbe)''' : aussitôt, sur-le-champ.<br>
'''αὐτοκίνητος, -ος, -ον (adjectif)''' : .<br>
'''αὐτοκινῶ (verbe)''' : .<br>
'''αὐτοκράτειρα, -ας (nom commun) (f)''' : impératrice.<br>
'''αὐτοκρατορία, -ας (nom commun) (f)''' : empire.<br>
'''αὐτοκράτωρ, -ορος (nom commun) (m)''' : empereur.<br>
'''αὐτολεξεί (adverbe)''' : mot pour mot.<br>
'''αὐτός -ή, -ό (pronom personnel)''' : il.<br>
'''αὐτοσχεδιάζω (verbe)''' : improviser.<br>
'''αὐτοσχεδιασμός, -οῦ (nom commun) (m)''' : improvisation.<br>
'''αὐτοσχέδιος, -ος, -ον (adjectif)''' : improvisé.<br>
'''αὐτοσχεδίως (verbe)''' : de façon improvisée.<br>
'''αὐτόχθων, -ων, -ον (adjectif)''' : indigène.<br>
'''αὐτοψία, -ας (nom commun) (f)''' : autopsie.<br>
'''αὐχήν, -ένος (nom commun) (m)''' : nuque.<br>
'''αὕω (verbe)''' : soustraire.<br>
'''ἀφαίρεσις, -έσεως (nom commun) (f)''' : soustraction.<br>
'''ἀφαιρῶ (verbe)''' : soustraire.<br>
'''ἀφεδρών, -ῶνος (nom commun) (m)''' : latrine.<br>
'''αφέλεια, -ίας (nom commun) (f)''' : ingénuité, naïveté.<br>
'''ἀφελής, -ής, -ές (adjectif)''' : ingénu, naïf.<br>
'''ἁφή, -ῆς (nom commun) (f)''' : toucher.<br>
'''ἀφήγημα, -ήματος (nom commun) (n)''' : récit.<br>
'''ἀφηγηματικός, -ή -όν (adjectif)''' : narratif.<br>
'''ἀφηγηματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἀφηγηματικός''.<br>
'''ἀφηγηματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἀφηγηματικός''.<br>
'''ἀφηγηματικώτατα, -, - (adverbe)''' : Superlatif de ''ἀντιφατικῶς''.<br>
'''ἀφηγηματικώτερον, -, - (adverbe)''' : Comparatif de ''ἀντιφατικῶς''.<br>
'''ἀφήγησις, -ήσεως (nom commun) (f)''' : narration.<br>
'''ἀφηγητής, -οῦ (nom commun) (m)''' : narrateur.<br>
'''ἀφηγοῦμαι (verbe)''' : narrer.<br>
'''ἄφθα, -ης (nom commun) (f)''' : aphte.<br>
'''ἀφίημι (verbe)''' : Envoyer, renvoyer. Laisser aller, lâcher, relâcher. Libérer.<br>
'''ἄφιξις, -ίξεως (nom commun) (f)''' : arrivée.<br>
'''ἀφόδευσις, -ύσεως (nom commun) (f)''' : défécation.<br>
'''ἀφοδεύω (verbe)''' : déféquer.<br>
'''ἀφοπλίζω (verbe)''' : désarmer.<br>
'''ἀφοπλισμός, -οῦ (nom commun) (m)''' : désarmement.<br>
'''ἀφροδισιακός -ή -όν (adjectif)''' : aphrodisiaque.<br>
'''ἀφροδισιασμός, -οῦ (nom commun) (m)''' : aphrodisiasme.<br>
'''ἀφροδισιαστικός, -ή -όν (adjectif)''' : aphrodisiastique.<br>
'''ἀφορισμός, -οῦ (nom commun) (m)''' : abolition, excommunication.<br>
'''ἀφροδίσιος, -α, -ον (adjectif)''' : vénérien.<br>
'''ἀφορίζω (verbe)''' : abolir, excommunier.<br>
'''ἀφοσίωσις, -ώσεως (nom commun) (f)''' : dévotion.<br>
'''ἀφρός, -οῦ (nom commun) (m)''' : écume.<br>
'''ἀφύη, -ῆς (nom commun) (f)''' : anchois.<br>
'''ἀναχαίνω (verbe)''' : retenir son souffle.<br>
'''ἀναχάσκω (verbe)''' : ouvrir la bouche.<br>
'''ἀχαριστῶ (verbe)''' : .<br>
'''ἁχατης, -ου (nom commun) (m)''' : agate.<br>
'''ἀχλυόεις, -εσσα, -εν (adjectif)''' : brumeux.<br>
'''ἀχλυοέντως (adverbe)''' : .<br>
'''ἀχλυοέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἀχλυόεις''.<br>
'''ἀχλυοέστερος, -έρα, -ερον (adjectif)''' : Comparatif de ''ἀχλυόεις''.<br>
'''ἄχλυσις, -εως (nom commun) (f)''' : .<br>
'''ἀχλύς, -ος (nom commun) (f)''' : brume, ténèbres.<br>
'''ἀχλυώδης, -ης, -ες (adjectif)''' : .<br>
'''ἀχλυῶ (verbe)''' : .<br>
'''ἀχράς, -δος (nom commun) (f)''' : poire.<br>
'''ἄχερδος, -έρδου (nom commun) (f)''' : poire.<br>
'''ἀχυρών, -ῶνος (nom commun) (m)''' : grange.<br>
'''ἀψίνθιον, -ίου (nom commun) (n)''' : absinthe.<br>
'''ἄψογος, -ος, -ον (n)''' : impeccable.<br>
'''ἄψ (adverbe)''' : En arrière (sans mouvement), en retour. Encore.<br>
'''ἄωτον, -ου (nom commun) (n)''' : summum.<br>
'''ἄω (verbe)''' : souffler, dormir.<br>
'''Ἀαρών (nom propre) (m)''' : Aaron.<br>
'''Ἀϐαδδών (nom propre) (m)''' : Abaddon.<br>
'''Ἀϐδίας, -ου (nom propre) (m)''' : Abdias.<br>
'''Ἅϐελ (nom propre) (m)''' : Abel.<br>
'''Ἀϐέλλα, -ας (nom propre) (f)''' : Avella.<br>
'''Ἀϐενέζερ (nom propre) (m)''' : Ebenezer.<br>
'''Ἀϐησσυνία, -ας (nom propre) (f)''' : Abyssinie.<br>
'''Ἀϐιά (nom propre) (m)''' : Abija.<br>
'''Ἀϐιάθαρ (nom propre) (m)''' : Abiathar.<br>
'''Ἀϐιγαία, -ας (nom propre) (m)''' : Abigaëlle.<br>
'''Ἀϐραάμ (nom propre) (m)''' : Abraham.<br>
'''Ἀϐράμης, -ου (nom propre) (m)''' : Forme alternative de ''Ἀϐραάμ''.<br>
'''Ἀϐραμίας, -ου (nom propre) (m)''' : Forme alternative de ''Ἀϐραάμ''.<br>
'''Ἄϐραμος, - (nom propre) (m)''' : Forme alternative de ''Ἀϐραάμ''.<br>
'''Ἀγαμέμνων, -ονος (nom propre) (m)''' : Agamemnon.<br>
'''Ἀγαθή, -ῆς (nom propre) (f)''' : Agathe.<br>
'''Ἀγαθοκλῆς, -έους (nom propre) (m)''' : Agathoclès.<br>
'''Ἀγήνωρ, -ορος (nom propre) (m)''' : Agénor.<br>
'''Ἀγησίλαος, -άου (nom propre) (m)''' : Agésilas.<br>
'''Αἰγεύς, -έως (nom propre) (m)''' : Égée.<br>
'''Αἰγιδιός, -οῦ (prénom) (m)''' : Gilles.<br>
'''Αἴγλη, -ης (nom propre) (f)''' : Églé.<br>
'''Αἰγόκερως, -έρωτος (nom commun) (f)''' : Capricorne.<br>
'''Ἄγκυρα, -ύρας (nom propre) (f)''' : Ankara.<br>
'''Ἀγλαΐα, -ας (nom propre) (f)''' : Aglaé.<br>
'''Ἀγλαός, -οῦ (nom propre) (m)''' : Aglaos.<br>
''', - (nom propre) (m)''' : Agrios.<br>
'''Ἁγνοδίκη, -ης (nom propre) (f)''' : Agnodice.<br>
'''Ἁγνωνίδης, -ου (nom propre) (m)''' : .<br>
'''Ἀγρίππας, -α (nom propre) (m)''' : Agrippa.<br>
'''Ἀγριππίνη, -ης (nom propre) (f)''' : Agrippine.<br>
'''Ἀδάμ (nom propre) (m)''' : Adam.<br>
'''ᾍδης, -ου (nom propre) (m)''' : [[wikt:Hadès|Hadès]].<br>
'''Ἀδικία, -ας (nom propre) (f)''' : Adicie.<br>
'''Ἁδριανούπολις, -όλεως (nom propre) (f)''' : Edirne.<br>
'''Ἁδριανός, -οῦ (nom propre) (m)''' : Hadrien.<br>
'''Ἄδωνις, -ώνιδος (nom propre) (m)''' : Adonis.<br>
'''Ἀζαζέλ (nom propre) (m)''' : Azazel.<br>
'''Ἀθανάσιος, -ίου (nom propre) (m)''' : Athanase.<br>
'''Ἀθάνα, -ας (nom propre) (f)''' : Forme dorienne de ''Ἀθηνᾶ''.<br>
'''Ἀθηνᾶ, -ᾶς (nom propre) (f)''' : [[wikt:Athéna|Athéna]].<br>
'''Ἀθῆναι, -ῶν (nom propre) (f)''' : Athènes.<br>
'''Ἀθηναία, -ας (nom propre) (f)''' : Forme de ''Ἀθηνᾶ''.<br>
'''Ἀθηναῖα, -ίας (nom commun) (f)''' : Athénienne.<br>
'''Ἀθηναῖος, -ίου (nom commun) (m)''' : Athénien.<br>
'''Ἀθήναιος, -ίου (nom propre) (m)''' : Athénée.<br>
'''Ἀθήνη, -ης (nom propre) (f)''' : Forme ionienne de ''Ἀθηνᾶ''.<br>
'''Ἀθηνόδωρος, -ώρου (nom propre) (m)''' : Athénodore.<br>
'''Ἀθύρ (nom propre) (m)''' : Athyr.<br>
'''Ἅθωρ (nom propre) (f)''' : Hathor.<br>
'''Ἄθως, -ω (nom propre) (m)''' : Athos (géant) ; mont Athos.<br>
'''Αἰακός, -οῦ (nom propre) (m)''' : Éaque.<br>
'''Αἴας, -ντος (nom propre) (m)''' : Ajax.<br>
'''Αἰαία, -ας (nom propre) (f)''' : Ééa.<br>
'''Ἀΐδας, -ου (nom propre) (m)''' : Forme dorienne de ''ᾍδης''.<br>
'''Ἀΐδης, -ου (nom propre) (m)''' : Forme homérique de ''ᾍδης''.<br>
'''Ἀϊδωνεύς, -έως (nom propre) (m)''' : Forme de ''ᾍδης''.<br>
'''Αἰγεύς, -έως (nom propre) (m)''' : Égée.<br>
'''Αἰγιδιός, -οῦ (prénom) (m)''' : Gilles.<br>
'''Αἴγισθος, -ίσθου (nom propre) (m)''' : Égisthe.<br>
'''Αἰγύπτια, -ίας (nom commun) (m)''' : Égyptienne.<br>
'''Αἰγυπτιακή, -ῆς (nom propre) (f)''' : égyptien (langue).<br>
'''Αἰγύπτιος, -ίου (nom commun) (m)''' : Égyptien.<br>
'''Αἴγυπτος, -ύπτου (nom propre) (m)''' : Égypte.<br>
'''Αἰήτης, -ου (nom propre) (m)''' : Éétès.<br>
'''Αἰκατερίνη, -ης (prénom) (f)''' : Catherine.<br>
'''Αἰθαλία, -ας (nom propre) (f)''' : Italie.<br>
'''Αἰθήρ, -έρος (nom propre) (m)''' : [[wikt:Éther|Éther]].<br>
'''Αἰθιοπία, -ας (nom propre) (f)''' : Éthiopie.<br>
'''Αἰθιοπίς, -δος (nom commun) (f)''' : Éthiopienne.<br>
'''Αἰθίοψ, -πος (nom commun) (m)''' : Éthiopien.<br>
'''Αἰμιλία, -ας (nom propre) (f)''' : Émilie.<br>
'''Αἰμίλιος, -ίου (nom propre) (m)''' : Émile.<br>
'''Αἰνέας, -ου (nom propre) (m)''' : Forme poétique de ''Αἰνείας''.<br>
'''Αἰνείας, -ου (nom propre) (m)''' : Énée.<br>
'''Αἰνειάς, -δος (nom propre) (f)''' : Énéide.<br>
'''Αἰολίς, -δος (nom propre) (f)''' : Éolide.<br>
'''Αἰολεύς, -έως (nom commun) (m)''' : Éolien.<br>
'''Αἴολος, -όλου (nom propre) (m)''' : [[wikt:Éole|Éole]].<br>
'''Αἴολος, -όλου (nom propre) (m)''' : Éole (fils d'Hellen).<br>
'''Αἶσα, -ἴσης (nom propre) (f)''' : [[wikt:Ésa|Ésa]].<br>
'''Αἴσακος, -άκου (nom propre) (m)''' : Ésaque,
'''Αἰσχύλος, -ου (nom propre) (m)''' : Eschyle.<br>
'''Αἴσωπος, -ώπου (nom propre) (m)''' : Ésope.<br>
'''Αἴτνη, -ης (nom propre) (f)''' : Etna.<br>
'''Αἰών, -ῶνου (nom propre) (m)''' : Éon.<br>
'''Ἀκαδημία, -ας (nom propre) (f)''' : jardin d’Académos, près d’Athènes, où Platon enseignait.<br>
'''Ἀκάδημος, -ήμου (nom propre) (m)''' : Académos.<br>
'''Ἄκις, -εως (nom propre) (m)''' : Acis.<br>
'''Ἀκταίων, -ος (nom propre) (m)''' : Actéon.<br>
'''Ἀκτέων, -ος (nom propre) (m)''' : Forme poétique d’''Ἀκταίων''.<br>
'''Ἄκτωρ, -ορος (nom propre) (m)''' : Actor (frère cadet d’Augias).<br>
'''Ἀκκώ, -οῦς (nom propre) (f)''' : Aléria.<br>
'''Ἀλαλίη, -ης (nom propre) (f)''' : Aléria.<br>
'''Ἀλϐίων, -ος (nom propre) (m)''' : Albion.<br>
'''Ἀλεξάνδρεια, -ίας (nom propre) (f)''' : Alexandrie.<br>
'''Ἀλεξανδρέττα, -ας (nom commun) (f)''' : Alexandrine.<br>
'''Ἀλεξανδρεύς, -έως (nom commun) (m)''' : Alexandrin.<br>
'''Ἀλέξανδρος, -άνδρου (nom propre) (m)''' : Alexandre.<br>
'''Ἀλέξανδρος ὁ Μέγας (nom propre) (m)''' : Alexandre le Grand.<br>
'''Ἄλεξις, - (nom propre) (m)''' : Alexis.<br>
'''Ἀληκτώ, -οῦς (nom propre) (f)''' : Alecto (une des Érynies).<br>
'''Ἀλήτης, -ου (nom propre) (m)''' : Alétès.<br>
'''Ἀλθαία, -ας (nom propre) (f)''' : Althée.<br>
'''Ἀλιλάτ (nom propre) (f)''' : Al-Lat.<br>
'''Ἀλκάθοος, -ου (prénom) (m)''' : Alcathoos.<br>
'''Ἀλκείδης, ου (nom propre) (m)''' : Alcide (Premier nom d’Héraclès.).<br>
'''Ἄλκης, -ους (nom propre) (m)''' : Alceste.<br>
'''Ἄλκηστις, -ήστιδος (nom propre) (f)''' : Alceste.<br>
'''Ἀλκιϐιάδης, ου (nom propre) (m)''' : Alcibiade.<br>
'''Ἀλκίνοος, -ου (nom propre) (m)''' : Alcinoos.<br>
'''Ἀλκμαίων, -ος (nom propre) (m)''' : Alcméon.<br>
'''Ἀλκμήνη, -ης (nom propre) (f)''' : Alcmène.<br>
'''Ἀλφιτώ, -οῦς (nom propre) (f)''' : Alphito.<br>
'''Ἀλώπηξ, -εκος (nom propre) (f)''' : Vulpecula.<br>
'''Ἀμϐρόσιος, -ίου (nom propre) (m)''': Ambroise.<br>
'''Ἀμαζών, -όνος (nom propre) (f)''' : Amazone.<br>
'''Ἀμένωφις, - (nom propre) (m)''': Aménophis.<br>
'''Ἄμηστρις, -δος (nom propre) (f)''' : Amestris.<br>
'''Ἀμαύνι (nom propre) (f)''' : Amemet.<br>
'''Ἀμπρακία, -ας (nom propre) (f)''' : Ambracie.<br>
'''Ἀμπρακιώτης, -ου (nom commun) (m)''' : Ambracien.<br>
'''Ἀμπρακιῶτις, -ώτιδος (nom commun) (f)''' : Ambracienne.<br>
'''Ἀμύντας, -ου (nom propre) (m)''' : Amyntas.<br>
'''Ἀμφιμέδων, -οντος (nom propre) (m)''' : Amphimédon.<br>
'''Ἀμφιστρεύς, -έως (nom propre) (m)''' : Amphitrée.<br>
'''Ἀμφιτρίτη, -ης (nom propre) (f)''' : Amphitrite.<br>
'''Ἀμφιτροπή, -ῆς (nom propre) (f)''' : Amphitropée.<br>
'''Ἀμφιτρύων, -ωνος (nom propre) (m)''' : Amphitryon.<br>
'''Ἀναξίϐια, -ας (nom propre) (f)''' : Anaxibie.<br>
'''Ἀναξίϐιος, -ίου (nom propre) (m)''' : Anaxibios.<br>
'''Ἀννίϐας, -ου (nom propre) (m)''' : Hannibal.<br>
'''Ἀνδρέας, -ου (prénom) (m)''' : André.<br>
'''Ἄννα, -ας (prénom) (f)''' : Anne.<br>
'''Ἀνάγκη, -ης (nom propre) (f)''' : Ananké.<br>
'''Ἀναῗτις, -ΐτεως (nom propre) (f)''' : Anaïs.<br>
'''Ἀναξαγόρας, -ου (nom propre) (m)''' : Anaxagore.<br>
'''Ἀναξίμανδρος, -ου (nom propre) (m)''' : Anaximandre.<br>
'''Ἀναξιμήνης, -ου (nom propre) (m)''' : Anaximène.<br>
'''Ἀναστασία, -ας (nom propre) (f)''' : Anastasie.<br>
'''Ἀναστάσιος, -ίου (nom propre) (m)''' : Anastase.<br>
'''Ἀνατόλιος, -ίου (nom propre) (m)''' : Anatole.<br>
'''Ἄνουϐις, -ύϐιδος (nom propre) (m)''' : [[wikt:Anubis|Anubis]].<br>
'''Ἄνουκις, -δος (nom propre) (f)''' : Anoukis.<br>
'''Ἀντέρως, -τος (nom propre) (m)''' : [[wikt:Antéros|Antéros]].<br>
'''Ἀντιγόνη, -ης (nom propre) (f)''' : Antigone.<br>
'''Ἀντίκλεια, -ας (nom propre) (f)''' : Anticlée (mère d'Ulysse).<br>
'''Ἀντικύθηρα, -ήρων (nom propre) (n)''' : Anticythère.<br>
'''Ἀντίνοoς, -όου (nom propre) (m)''' : Antinoüs.<br>
'''Ἀντίπολις, -όλεως (nom propre) (f)''' : Antipolis.<br>
'''Ἀντισθένης, -ους (nom propre) (m)''' : Antisthène. (Philosophe né vers 444 av. J.-C. et décédé vers 365 av. J.-C.)<br>
'''Ἀντωνία, -ας (nom propre) (f)''' : Antonia.<br>
'''Ἀντώνιος, -ίου (nom propre) (m)''' : Antoine.<br>
'''Ἄντων, -ου (nom propre) (m)''' : Anton.<br>
'''Ἀξιός, -οῦ (nom propre) (m)''' : Axius.<br>
'''Ἀπάτη, -ης (nom propre) (f)''' : Apaté.<br>
'''Ἀπιδανός, -οῦ (nom propre) (m)''' : Apidanus.<br>
'''Ἆπις, Ἄπιδος (nom propre)''' : Apis.<br>
'''Ἀπολλόδωρος, -ώρου (nom propre) (m)''' : Apollodore.<br>
'''Ἀπόλλων, -ωνος (nom propre) (m)''' : [[wikt:Apollon|Apollon]].<br>
'''Ἀρά, -ᾶς (nom propre) (f)''' : [[wikt:Ara|Ara]].<br>
'''Ἀραϐία, -ας (nom propre) (f)''' : Arabie.<br>
'''Ἀράχνη, -ης (nom propre) (f)''' : Arachné.<br>
'''Ἄραψ, -ϐος (nom commun) (m/f)''' : Arabe.<br>
'''Ἀραράτ (nom propre) (m)''' : Ararat.<br>
'''Ἀργαῖος, -ίου (nom propre) (m)''' : Argaïos.<br>
'''Ἀργοναῦτης, -ύτου (nom commun) (m)''' : Argonaute.<br>
'''Ἀργῷος, -ῴος, -ῷον (adjectif)''' : .<br>
'''Ἀργώ, -οῦς (nom propre) (f)''' : Argo.<br>
'''Ἀρετή, -ῆς (nom propre) (f)''' : Arété (déesse).<br>
'''Ἄρευς, -εως (nom propre) (m)''' : Forme éolienne de ''Ἄρης''.<br>
'''Ἄρης, -εως (nom propre) (m)''' : [[wikt:Arès|Arès]].<br>
'''Ἀρήτη, -ης (nom propre) (f)''' : Arété (philosophe grecque de l'école des Cyrénaiques).<br>
'''Ἀρίσταρχος, -άρχου (nom commun) (m)''' : Aristarque.<br>
'''Ἀριστόϐουλος, -ύλου (nom propre) (m)''' : Aristobule.<br>
'''Ἀριστογείτων, -ονος (nom propre) (m)''' : Aristogiton.<br>
'''Ἀριστοτέλης, -ους (nom propre) (m)''' : Aristote.<br>
'''Ἀριστώνυμος, -ύμου (nom propre) (m)''' : Aristonyme.<br>
'''Ἀρίων, -ονος (nom propre) (m)''' : Arion.<br>
'''Ἀρκαδία, -ας (nom propre) (f)''' : Arcadie.<br>
'''Ἀρκάς, -δος (nom commun) (m/f)''' : Arcadien ; Arcadienne.<br>
'''Ἀρκτοῦρος, -ύρου (nom propre) (m)''' : Arcturus.<br>
'''Ἁρμαγεδών (nom propre) (m)''' : Armageddon.<br>
'''Ἁρμόδιος, -ίου (nom propre) (m)''' : Harmodios.<br>
'''Ἀρσένιος, -ίου (nom propre) (m)''' : Arsène.<br>
'''Ἀρσινόη, -ης (nom propre) (f)''' : Arsinoé.<br>
'''Ἀρταξέρξης, -ου (nom propre) (m)''' : Artaxerxès.<br>
'''Ἄρτεμις, -έμιδος (nom propre) (f)''' : [[wikt:Artémis|Artémis]].<br>
'''Ἀρχίας, -ου (nom propre) (m)''' : Archias.<br>
'''Ἀρχιμήδης, -ους (nom propre) (m)''' : Archimède.<br>
'''Ἀσάνα, -ας (nom propre) (f)''' : Forme dorienne de ''Ἀθηνᾶ''.<br>
'''Ἀσδρούϐας, -ου (nom propre) (m)''' : Hasdrubal (véritable nom de Clitomaque de Carthage).<br>
'''Ἀσήρ (nom propre) (m)''' : Aser.<br>
'''Ἀσκαλωνίτης, -ου (nom commun) (m)''' : Ascalonite.<br>
'''Ἀσκαλωνῖτις, -ίτιδος (nom commun) (f)''' : Ascalonite.<br>
'''Ἀσκάλων, -ος (nom propre) (m)''' : Ascalon.<br>
'''Ἀσκληπιός, -οῦ (nom propre) (m)''' : [[wikt:Asclépios|Asclépios]].<br>
'''Ἀσμοδαῖος, -ίου (nom propre) (m)''' : Asmodée.<br>
'''Ἀσεννέθ (nom propre) (m)''' : Asnath.<br>
'''Ἀσπαθίνης, -ου (nom propre) (m)''' : Aspathinès.<br>
'''Ἀσουήρος, -ου (nom propre) (m)''' : Assuérus.<br>
'''Ἀσσυρία, -ας (nom propre) (f)''' : Assyrie.<br>
'''Ἀσσύριος, -ίου (nom commun) (m)''' : Assyrien.<br>
'''Ἀστάρτη, -ης (nom propre) (f)''' : Astarté.<br>
'''Ἀστυάναξ, -κτος (nom propre) (m)''' : Astyanax.<br>
'''Ἀστυάνασσα, -ας (nom propre) (f)''' : Astyanassa.<br>
'''Ἄστυ, -εως (nom propre) (n)''' : Athènes.<br>
'''Ἄτη, -ης (nom propre) (f)''' : [[wikt:Até|Até]]. (Déesse de l’égarement.)<br>
'''Ἀττική, -ῆς (nom propre) (f)''' : Attique.<br>
'''Ἀτλαντίς, -δος (nom propre) (f)''' : (Toponymie) océan Atlantique. (Mythologie) Atlantide.<br>
'''Ἄτλας, -αντος (nom propre) (m)''' : Atlas.<br>
'''Ἄτροπος, -όπου (nom propre) (f)''' : Atropos (troisième Moire).<br>
'''Αὐγείας, -ου (nom propre) (m)''' : Augias.<br>
'''Αὐλίς, -δος (nom propre) (f)''' : Aulis.<br>
'''Αὐνάν (nom propre) (m)''' : Onan.<br>
'''Αὐξώ, -οῦς (nom propre) (f)''' : Auxo.<br>
'''Αὐσονία, -ας (nom propre) (f)''' : Italie.<br>
'''Αὐσονίη, -ης (nom propre) (f)''' : Forme alternative de ''Αὐσονία''.<br>
'''Αὔσων, -ονος (nom propre) (m)''' : Auson (fils d’Ulysse.)<br>
'''Αὐτόλυκος, -ύκου (nom propre) (m)''' : Autolycos (Aïeul maternel d’Ulysse.)<br>
'''Αὐτομέδων, -οντος (nom propre) (m)''' : Automédon (Conducteur du char d’Achille.)<br>
'''Αὔως, -ω (nom propre) (f)''' : Forme alternative de ''Ἕως''.<br>
'''Ἀφρική, -ῆς (nom propre) (f)''' : Afrique.<br>
'''Ἀφροδίτη, -ης (nom propre) (f)''' : Aphrodite.<br>
'''Ἀφρόδιτος, -ίτου (nom propre) (m)''' : Aphroditos.<br>
'''Ἀφρώ, -οῦς (nom propre) (f)''' : Aphrô.<br>
'''Ἀχαΐα, -ας (nom propre) (f)''' : Achaïe.<br>
'''Ἀχαιμένης, -ους (nom propre) (m)''' : Achéménès.<br>
'''Ἀχαιμενίδης, -ου (nom propre) (m)''' : Achéménide.<br>
'''Ἀχαιός, -οῦ (nom propre) (m)''' : Achaïos (fils de Xouthos).<br>
'''Ἀχατης, -ου (nom propre) (m)''' : Achatès (fleuve de Sicile).<br>
'''Ἀχελῷος, -ῴου (nom propre) (m)''' : Achéloos.<br>
'''Ἀχέρων, -οντος (nom propre) (m)''' : Achéron.<br>
'''Ἀχιλλεύς, -έως (nom propre) (m)''' : Achille.<br>
'''Ἀωσφόρος, -ου (nom propre) (m)''' : Forme dorienne de ''Ἑωσφόρος''.<br>
==Β==
'''βαθμίς, -δος (nom commun) (f)''' : échelon.<br>
'''βάθρον, -ου (nom commun) (n)''' : podium.<br>
'''βαθέως (adverbe)''' : profondément.<br>
'''βάθος, -ους (nom commun) (n)''' : Profondeur, hauteur.<br>
'''βαθύς, -εῖα, -ύ (adjectif)''' : profond.<br>
'''βαθύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''βαθύς''.<br>
'''βαθύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''βαθύς''.<br>
'''βαΐον, -ου (nom commun) (n)''' : rameau.<br>
'''βακτηρία, -ας (nom commun) (f)''' : Bâton de marche ; bâton employé comme insigne de juge.<br>
'''βαλανεῖον, -ίου (nom commun) (n)''' : bain (lieu public).<br>
'''βαλανεύς, -έως (nom commun) (n)''' : plongeur.<br>
'''βάλανος, -άνου (nom commun) (f)''' : Gland de chêne. (Botanique) Datte. Fermoir d’un collier. Pêne d’un verrou. Moule de mer (poisson). Noix, châtaigne. Suppositoire.<br>
'''βαλάντιον, -ίου (nom commun) (n)''' : bourse (sac d'argent).<br>
'''βαλλίζω (verbe)''' : danser.<br>
'''βάμϐαξ, -κος (nom commun) (m)''' : coton.<br>
'''βαμϐακερός, -ή, -όν (adjectif)''' : .<br>
'''βαμϐακηρός, -ά, -όν (adjectif)''' : .<br>
'''βαμϐάκινος, -ίνου (nom commun) (n)''' : .<br>
'''βαμϐάκιον, -ίου (nom commun) (n)''' : .<br>
'''βαμϐακοειδής, -ής, -ές (adjectif)''' : .<br>
'''βανά, -ᾶς (nom commun) (f)''' : Forme béotienne de ''γυνή''.<br>
'''βάπτω (verbe)''' : plonger, teindre ; baptiser.<br>
'''βάρϐαρα, -ας (nom commun) (f)''' : barbare.<br>
'''βάρϐαρικός, -ή, -όν (adjectif)''' : barbare.<br>
'''βάρϐαρικῶς (adverbe)''' : barbarement.<br>
'''βάρϐαρικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''βάρϐαρικός''.<br>
'''βάρϐαρικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''βάρϐαρικός''.<br>
'''βάρϐαρικώτατα, -, - (adverbe)''' : Superlatif de ''βάρϐαρικῶς''.<br>
'''βάρϐαρικώτερον, -, - (adverbe)''' : Comparatif de ''βάρϐαρικῶς''.<br>
'''βάρϐαρος, -άρου (nom commun) (m)''' : barbare.<br>
'''βαρέως (adverbe)''' : lourdement.<br>
'''βᾶρις, -άριδος (nom commun) (f)''' : barque.<br>
'''βᾶρκις, -άρκιδος (nom commun) (f)''' : barque.<br>
'''βαρύς, -εῖα, -ύ (adjectif)''' : lourd.<br>
'''βαρύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''βαρύς''.<br>
'''βαρύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''βαρύς''.<br>
'''βασιλεία, -ας (nom commun) (f)''' : pouvoir royal ; royauté. Royaume.<br>
'''βασίλεια, -ίας (nom commun) (f)''' : princesse. (héritière du souverain régnant)<br>
'''βασιλείδης, -ου (nom commun) (m)''' : prince. (héritier du souverain régnant)<br>
'''βασίλειος, -ία, -ιον (adjectif)''' : royal.<br>
'''βασιλεύς, -έως (nom commun) (m)''' : roi.<br>
'''βασιλήϊος, -, - (adjectif)''' : Forme ionienne de ''βασίλειος''.<br>
'''βασιλῇος, -, - (adjectif)''' : Forme éolienne de ''βασίλειος''.<br>
'''βασιλικός, -ή, -όν (adjectif)''' : royal.<br>
'''βασίλισσα, -ας (nom commun) (f)''' : reine.<br>
'''βάσις, -εως (nom commun) (f)''' : Action de marcher, marche. Organe pour la marche. Ce sur quoi l’on marche ou l’on se tient.<br>
'''βάσκανος, -ος, -ον (adjectif)''' : Maléfique ; médisant.<br>
'''βασκάνως (adverbe)''' : maléfiquement.<br>
'''βασκανώτατος, -, - (adverbe)''' : Superlatif de ''βάσκανος''.<br>
'''βασκανώτερος, -, - (adverbe)''' : Comparatif de ''βάσκανος''.<br>
'''βασσάρα, -ας (nom commun) (f)''' : renard.<br>
'''βαστάζω (verbe)''' : lever, soulever. Relever. Tenir dans ses bras ou ses mains.<br>
'''βάτος, -ου (nom commun) (f)''' : Ronce, buisson. Épine.<br>
'''βατός, -ή, -όν (adjectif)''' : accessible.<br>
'''βάτραχος, -άχου (nom commun) (m/f)''' : Crapaud ; grenouille.<br>
'''βατῶ (verbe)''' : Forme phocienne de ''πατῶ''.<br>
'''βαυϐάω (verbe)''' : s’endormir.<br>
'''βαυϐών, -ῶνος (nom commun) (m)''' : godemichet.<br>
'''βαυϐώ, -οῦς (nom commun) (f)''' : nourrice.<br>
'''βαυκάλημα, -ήματος (nom commun) (n)''' : berceuse.<br>
'''βαυκίζομαι (verbe)''' : être prude.<br>
'''βαυκός, -ή, -όν (adjectif)''' : prude.<br>
'''βαυκότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''βαυκός''.<br>
'''βαυκότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''βαυκός''.<br>
'''βαυκῶς (adverbe)''' : prudemment.<br>
'''βαυκώτατα, -, - (adverbe)''' : Superlatif de ''βαυκῶς''.<br>
'''βαυκώτερον, -, - (adverbe)''' : Comparatif de ''βαυκῶς''.<br>
'''βαύ (onomatopée)''' : ouaf.<br>
'''βαφεύς, -έως (nom commun) (m)''' : peintre.<br>
'''βδαλεύς, -έως (nom commun) (m)''' : suceur.<br>
'''βδάλσις, - (nom commun) (f)''' : succion.<br>
'''βδάλλω (verbe)''' : Sucer, téter. Traire le lait.<br>
'''βδέλλα, -ης (nom commun) (f)''' : sangsue.<br>
'''βδέω (verbe)''' : péter (flatuler)<br>
'''βεϐαιῶ (verbe)''' : garantir (se rendre garant de la valeur, de la qualité d’une chose).<br>
'''βέϐηλος, -ος, -ον (adjectif)''' : Franchissable, profane. Impur, interdit.<br>
'''βεϐηλότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''βέϐηλος''.<br>
'''βεϐηλότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''βέϐηλος''.<br>
'''βέϐηλως (adverbe)''' : profanement.<br>
'''βεϐηλώτατα, -, - (adverbe)''' : Superlatif de ''βεϐρῶς''.<br>
'''βεϐηλώτερον, -, - (adverbe)''' : Comparatif de ''βεϐρῶς''.<br>
'''βεϐρός, -ά, -όν (adjectif)''' : stupide.<br>
'''βεϐρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''βεϐρός''.<br>
'''βεϐρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''βεϐρός''.<br>
'''βεϐρῶς (adverbe)''' : stupidement.<br>
'''βεϐρώτατα, -, - (adverbe)''' : Superlatif de ''βεϐρῶς''.<br>
'''βεϐρώτερον, -, - (adverbe)''' : Comparatif de ''βεϐρῶς''.<br>
'''βελόνη, -ης (nom commun) (f)''' : aiguille.<br>
'''βέλος, -ους (nom commun) (n)''' : flèche.<br>
'''βέλτιστος, -ίστη, -έλτιστον (adjectif)''' : Superlatif de ''ἀγαθός''.<br>
'''βελτίων, -ων, -έλτιον (adjectif)''' : Comparatif de ''ἀγαθός''.<br>
'''βένθος, -ους (nom commun) (n)''' : benthos.<br>
'''βερϐέριον, -ίου (nom commun) (n)''' : .<br>
'''βεῦδος, -ύδους (nom commun) (n)''' : .<br>
'''βέφυρα, -ύρας (nom commun) (f)''' : Forme béotienne de ''γέφυρα ''.<br>
'''βῆ βῆ (onomatopée)''' : bêlement.<br>
'''βῆμα, -ήματος (nom commun) (n)''' : .<br>
'''βήξ, -χός (nom commun) (m)''' : toux.<br>
'''βήσσω (verbe)''' : tousser.<br>
'''βῆτα (nom commun) (n)''' : bêta.<br>
'''βήττω (verbe)''' : Forme attique ''βήσσω''.<br>
'''βία, -ας (nom commun) (f)''' : Force, violence.<br>
'''βιάζω (verbe)''' : Contraindre, forcer. (Au passif) Être contraint, forcé soumis.<br>
'''βιϐάζω (verbe)''' : Faire aller ; exalter.<br>
'''βιϐλαρίδιον, -ίου (nom commun) (n)''' : livret.<br>
'''βιϐλιοθήκη, -ης (nom commun) (f)''' : bibliothèque.<br>
'''βιϐλίον, -ου (nom commun) (n)''' : livre.<br>
'''βιϐλιοπωλεῖον, -ίου (nom commun) (n)''' : librairie.<br>
'''βιϐλιοπώλης, -ου (nom commun) (m)''' : libraire.<br>
'''βίϐλος, -ου (nom commun) (f)''' : Papier fait à partir de l'écorce de papyrus.<br>
'''βιϐρώσκω (verbe)''' : Dévorer, manger avec avidité. (Figuré) Dévorer ou engloutir (une fortune).<br>
'''βίος, -ου (nom commun) (m)''' : vie.<br>
'''βίωσις, -ώσεως (nom commun) (f)''' : manière de vie.<br>
'''βινῶ (verbe)''' : foutre, baiser (avoir un rapport sexuel).<br>
'''βιῶ (verbe)''' : être, vivre.<br>
'''βλαδαρός, -ά, -όν (adjectif)''' : flasque.<br>
'''βλαδύς, -εῖα, -ύ (adjectif)''' : faible.<br>
'''βλαδυτής, -ῆτος (nom commun) (f)''' : faiblesse.<br>
'''βλακότατα, -, - (adverbe)''' : Superlatif de ''βλακωδῶς''.<br>
'''βλακότερον, -, - (adverbe)''' : Comparatif de ''βλακωδῶς''.<br>
'''βλακωδῶς (adverbe)''' : stupidement.<br>
'''βλακώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''βλάξ''.<br>
'''βλακώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''βλάξ''.<br>
'''βλαπτικός, -ή, -όν (adjectif)''' : .<br>
'''βλαπτικῶς (adverbe)''' : -ment.<br>
'''βλαπτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''βλαπτικός''.<br>
'''βλαπτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''βλαπτικός''.<br>
'''βλαπτικώτατα, -, - (adverbe)''' : Superlatif de ''βλαπτικῶς''.<br>
'''βλαπτικώτερον, -, - (adverbe)''' : Comparatif de ''βλαπτικῶς''.<br>
'''βλάπτω (verbe)''' : Léser, endommager. (Au passif) Éprouver un accident. Gêner, embarrasser. (En parlant de l’esprit) Troubler sa raison. (Postérieur) Faire du tort, nuire.<br>
'''βλάξ, -κός (nom commun) (m/f)''' : stupide.<br>
'''βλαισός, -ή, -όν (adjectif)''' : balbutiant.<br>
'''βλέμμα, -τος (nom commun) (n)''' : Regard. (Au pluriel) Yeux.<br>
'''βλεννογόνος, -ου (nom commun) (m)''' : muqueuse.<br>
'''βλεννός, -οῦ (nom commun) (m)''' : mucus.<br>
'''βλέπω (verbe)''' : voir.<br>
'''βλεφαρίς, -δος (nom commun) (f)''' : cil.<br>
'''βλεφαρῖτις, -ίτης (nom commun) (f)''' : blépharite.<br>
'''βλέφαρον, -άρου (nom commun) (n)''' : paupière.<br>
'''βλῆμα, -ήματος (nom commun) (n)''' : projectile.<br>
'''βοή, -ῆς (nom commun) (f)''' : clameur.<br>
'''βοήθεια, -ας (nom commun) (f)''' : aide.<br>
'''βοηθός, -ός, -όν (adjectif)''' : aidant.<br>
'''βοηθῶ (verbe)''' : aider.<br>
'''βοιώτιος, -, - (adjectif)''' : béotien.<br>
'''βόλλα, -ας (nom commun) (f)''' : Forme éolienne de ''βουλή''.<br>
'''βορά, -ᾶς (nom commun) (f)''' : proie.<br>
'''βόρειος, -ία, -ιον (adjectif)''' : septentrional.<br>
'''βορειώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''βόρειος''.<br>
'''βορειώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''βόρειος''.<br>
'''βορειώτατα, -, - (adverbe)''' : Superlatif de ''βόρειως''.<br>
'''βορειώτερον, -, - (adverbe)''' : Comparatif de ''βόρειως''.<br>
'''βόρειως (adverbe)''' : septentrionalement.<br>
'''βόμϐος, -ου (nom commun) (m)''' : bourdonnement.<br>
'''βομϐυλιός, -οῦ (nom commun) (m)''' : .<br>
'''βόμϐυξ, -κος (nom commun) (m)''' : ver à soie ; sorte de flûte.<br>
'''βόρειος, -α, -ον (adjectif)''' : septentrional.<br>
'''βορρᾶς, -ᾶ (nom commun) (m)''' : nord.<br>
'''βόσκω (verbe)''' : Nourrir. Paître, se nourrir (en parlant des animaux)<br>
'''βόστρυχος, -ύχου (nom commun) (m)''' : boucle de cheveux.<br>
'''βοτάνη, -ης (nom commun) (f)''' : herbe.<br>
'''βοτανηφάγος, -ος, -ον (adjectif)''' : herbivore.<br>
'''βοτανηφόρος, -ος, -ον (adjectif)''' : enherbé.<br>
'''βοτανίδιον, -ίου (nom commun) (n)''' .<br>
'''βοτανίζω (verbe)''' : désherber.<br>
'''βοτάνιον, -ίου (nom commun) (n)''' : .<br>
'''βοτανικός, -ή, -όν (adjectif)''' : botanique.<br>
'''βοτανισμός, -οῦ (nom commun) (m)''' : botanisme.<br>
'''βοτανολογία ''' : botanologie.<br>
'''βοτανολόγος ''' : herboriste.<br>
'''βοτανολογῶ (verbe)''' : herboriser.<br>
'''βοτανώδης, -ης, -ης (adjectif)''' : botanique.<br>
'''βοτήρ, -ῆρος (nom commun) (m)''' : berger ; pasteur.<br>
'''βοτόν, -οῦ (nom commun) (n)''' : bétail.<br>
'''βουϐών, -ῶνος (nom commun) (m)''' : aine.<br>
'''βουίζω (verbe)''' : bourdonner.<br>
'''βουκολικός, -ή, -όν (adjectif)''' : pastoral.<br>
'''βουκολικῶς (adverbe)''' : pastoralement.<br>
'''βουκολικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''βουκολικικός''.<br>
'''βουκολικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''βουκολικικός''.<br>
'''βουκολικώτατα, -, - (adverbe)''' : Superlatif de ''βουκολικικῶς''.<br>
'''βουκολικώτερον, -, - (adverbe)''' : Comparatif de ''βουκολικικῶς''.<br>
'''βουκόλος, -ου (nom commun) (m)''' : bouvier.<br>
'''βουλευτήριον, -ίου (nom commun) (n)''' : bouleutérion.<br>
'''βουλή, -ῆς (nom commun) (f)''' : Volonté, vouloir. Décision, conseil. Sénat athénien.<br>
'''βούλησις, -ήσεως (nom commun) (f)''' : volonté.<br>
'''βούπρηστις, -ήστιδος (nom commun) (f)''' : bupreste.<br>
'''βουνός, -οῦ (nom commun) (m)''' : mont, montagne.<br>
'''βοῦς, -ός (nom commun) (m/f)''' : bœuf, vache.<br>
'''βουστροφηδόν (adverbe)''' : En écrivant alternativement de gauche à droite, puis de droite à gauche.<br>
'''βούτημα, -ήματος (nom commun) (n)''' : biscuit.<br>
'''βούτυρον, -ύρου (nom commun) (n)''' : beurre.<br>
'''βραδινός, -ή -όν (adjectif)''' : Forme éolienne de ''ῥαδινός''.<br>
'''βραϐευτής, -οῦ (nom commun) (m)''' : arbitre.<br>
'''βραδέως (adverbe)''' : lentement.<br>
'''βραδύς, -εῖα, -ύ (adjectif)''' : lent.<br>
'''βραδύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''βραδύς''.<br>
'''βραδύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''βραδύς''.<br>
'''βραδυτής, -ῆτος (nom commun) (f)''' : lenteur.<br>
'''βράγος, -ους (nom commun) (n)''' : bas-fond.<br>
'''βραχέως (adverbe)''' : courtement.<br>
'''βράχος, -ου (nom commun) (m)''' : écueil.<br>
'''βραχύς, -εῖα, -ύ (adjectif)''' : court.<br>
'''βραχύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''βραχύς''.<br>
'''βραχύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''βραχύς''.<br>
'''βρέμω (verbe)''' : gronder, retentir.<br>
'''βρένθος, -ου (nom commun) (m)''' : fierté.<br>
'''βρέφος, -ους (nom commun) (n)''' : fœtus, nouveau-né.<br>
'''βρεχμός, -οῦ (nom commun) (m)''' : .<br>
'''βρία, -ης (nom commun) (f)''' : ville.<br>
'''βρίμημα, -ήματος (nom commun) (n)''' : .<br>
'''βρεττανικός, -ή, -όν (adjectif)''' : breton insulaire.<br>
'''βρεττανικῶς (adverbe)''' : en breton insulaire.<br>
'''βρεττανικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''βρεττανικός''.<br>
'''βρεττανικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''βρεττανικός''.<br>
'''βρεττανικώτατα, -, - (adverbe)''' : Superlatif de ''βρεττανικῶς''.<br>
'''βρεττανικώτερον, -, - (adverbe)''' : Comparatif de ''βρεττανικῶς''.<br>
'''βρόμος, -ου (nom commun) (m)''' : Frémissement, grondement. Pétillement du feu. Grondement du tonnerre.<br>
'''βροντή, -ῆς (nom commun) (f)''' : Tonnerre. Stupeur.<br>
'''βροχίς, -δος (nom commun) (f)''' : .<br>
'''βρόχος, -ου (nom commun) (m)''' : nœud coulant.<br>
'''βρυγμός, -οῦ (nom commun) (m)''' : bruxisme.<br>
'''βρύον, -ου (nom commun) (n)''' : mousse (plante).<br>
'''βρυχηθμός, -οῦ (nom commun) (m)''' : rugissement.<br>
'''βρυχός, -οῦ (nom commun) (m)''' : brycose.<br>
'''βρυχῶμαι (verbe)''' : rugir.<br>
'''βύας, -ου (nom commun) (m)''' : hibou.<br>
'''βύρσα, -ας (nom commun) (f)''' : outre, cuir.<br>
'''βρώσιμος, -ος, -ον (adjectif)''' : mangeable.<br>
'''βρῶσις, -ώσεως (nom commun) (f)''' outre, cuir.<br>
'''βωϐός, -ή -όν (adjectif)''' : muet.<br>
'''βωλά, -ᾶς (nom commun) (f)''' : Forme dorienne de ''βουλή''.<br>
'''Βάαλ (nom propre) (m)''' : Baal.<br>
'''Βαϐυλωνεύς, -έως (nom commun) (m)''' : Babylonien.<br>
'''Βαϐυλωνία, -ας (nom commun) (f)''' : Babylonie.<br>
'''Βαϐυλωνιακός, -ός, -όν (adjectif)''' : babylonien.<br>
'''Βαϐυλώνιος, -ος, -ον (adjectif)''' : babylonien.<br>
'''Βαϐυλωνίς, -δος (nom commun) (f)''' : Babylonienne.<br>
'''Βαϐυλών, -ῶνος (nom propre) (f)''' : Babylone.<br>
'''Βάκχος, -ου (nom propre) (m)''' : Bacchus. (Épithète de Dionysos, parfois de Zeus.)<br>
'''Βαλλά, -ᾶς (nom propre) (f)''' : Bilha.<br>
'''Βαρϐάρα, -ας (nom propre) (f)''' : Barbara.<br>
'''Βαρϐαρικόν, -οῦ (nom propre) (m)''' : Barbaricum.<br>
'''Βαρθολομαῖος, -ίου (nom propre) (m)''' : Barthélémy.<br>
'''Βασίλειος, -ίου (nom propre) (m)''' : Basile.<br>
'''Βαταυΐα, -ας (nom propre) (f)''' : Batavie.<br>
'''Βατραχομυομαχία, -ας (nom propre) (f)''' : Bataille des grenouilles et des rats. (Parodie de l’''Iliade'' attribuée à un dénommé Pigrès d’Halicarnasse par Plutarque.)<br>
'''Βάττος, -ου (nom propre) (m)''' : Battos.<br>
'''Βαυϐώ, -οῦς (nom propre) (f)''' : Baubo.<br>
'''Βαῦκος, -ύκου (nom propre) (m)''' : Baukos.<br>
'''Βεελφεγώρ (nom propre) (m)''' : Belphégor.<br>
'''Βενδῖς, -ίδος (nom propre) (f)''' : Bendis.<br>
'''Βερενίκη, -ης (nom propre) (f)''' : Bérénice.<br>
'''Βηθανία, -ας (nom propre) (f)''' : Béthanie.<br>
'''Βηθλεέμ (nom propre) (f)''' : Bethléem.<br>
'''Bηλησαμα, -ας (nom propre) (f)''' : Belisama.<br>
'''Βῆλος, -ήλου (nom propre) (m)''' : Bel.<br>
'''Βηρυτός, -οῦ (nom propre) (f)''' : Beyrouth.<br>
'''Βία, -ας (nom propre) (m)''' : Bia.<br>
'''Βλάσιος, -ίου (nom propre) (m)''' : Blaise.<br>
'''Βλάχος, -ου (nom commun) (m)''' : Valaque.<br>
'''Βοανεργές (nom propre) (m)''' : Boanergès.<br>
'''Βοιωτία, -ας (nom propre) (f)''' : Béotie. (Région de Grèce centrale.)<br>
'''Βοιωτός, -οῦ (nom commun) (m)''' : Béotien.<br>
'''Βοιωτίς, -δος (nom commun) (f)''' : Béotienne.<br>
'''Βορέας, -ου (nom propre) (m)''' : Borée. (dieu du vent du Nord)<br>
'''Βορέης, -ου (nom propre) (m)''' : Forme ionienne de ''Βορέας''.<br>
'''Βορρᾶς, -ᾶ (nom propre) (m)''' : Forme attique de ''Βορέας''.<br>
'''Βοσπορίτης, -ου (nom commun) (m)''' : Bosphorite.<br>
'''Βόσπορος, -όρου (nom propre) (m)''' : Bosphore.<br>
'''Βούϐαστις, -άστιος (nom propre) (f)''' : Bastet.<br>
'''Βουκέφαλος, -άλου (nom propre) (m)''' : Bucéphale (Cheval préféré d’Alexandre le Grand.)<br>
'''Βουτώ, -οῦς (nom propre) (f)''' : Ouadjet.<br>
'''Βρέννος, -ου (nom commun) (m)''' : Brennos.<br>
'''Βρεττανία, -ας (nom propre) (f)''' : Bretagne (province romaine).<br>
'''Βρεττανίς, -δος (nom commun) (f)''' : Bretonne insulaire.<br>
'''Βρεττανός, -οῦ (nom commun) (m)''' : Breton insulaire.<br>
'''Βύϐλος, -ου (nom commun) (f)''' : Byblos.<br>
'''Βυζάντιον, -ίου (nom propre) (n)''' : Byzance.<br>
'''Βύζας, -αντος (nom propre) (m)''' : Byzas.<br>
'''Βιθυνία, -ας (nom propre) (f)''' : Bithynie.<br>
'''Βιθυνίς, -δος (nom commun) (f)''' : Bithynienne.<br>
'''Βιθυνός, -οῦ (nom commun) (o)''' : Bithynien.<br>
==Γ==
'''γα (particule)''' : Forme dorienne et béotienne de ''γε''.<br>
'''γαίω (verbe)''' : exulter, se réjouir.<br>
'''γάλα, -κτος (nom commun) (n)''' : lait.<br>
'''γαλανός, -ός, -όν (adjectif)''' : Forme dorienne de ''γαληνός''.<br>
'''γαλήνη, -ης, -ης (nom commun) (f)''' : Calme de la mer. (Par extension) Calme, sérénité. Galène. Antidote contre les morsures de vipères.<br>
'''γαληνός, -ός, -όν (adjectif)''' : calme (spécialement à propos de la mer).<br>
'''γαλέη, -ης (nom commun) (f)''' : belette.<br>
'''γαλῆ, -ς (nom commun) (f)''' : Forme alternative de ''γαλέη''.<br>
'''γαλιῶ (verbe)''' : Être lascif comme une belette.<br>
'''γάμμα (nom commun) (n)''' : gamma.<br>
'''γαμέτης, -ου (nom commun) (m)''' : époux, mari.<br>
'''γαμῶ (verbe)''' : se marier (quand on parle d’un homme)<br>
'''γάργαλος, -άλου (nom commun) (m)''' : .<br>
'''γαργαλίζω (verbe)''' : chatouiller.<br>
'''γαργαλισμός, -οῦ (nom commun) (m)''' : chatouille.<br>
'''γαργαρεών, -ῶνος (nom commun) (m)''' : luette.<br>
'''γάρ (conjonction)''' : car ; en effet.<br>
'''γαστήρ, -τρός (nom commun) (f)''' : ventre.<br>
'''γαῦρος, -ύρη, -ῦρον (adjectif)''' : Exultant, joyeux. Hautain, dédaigneux.<br>
'''γαυρότατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''γαῦρος''.<br>
'''γαυρότερος, -έρα, -ερον (adjectif)''' : Comparatif de ''γαῦρος''.<br>
'''γαῦρως (adverbe)''' : joyeusement ; hautainement, dédaigneusement.<br>
'''γαυρότης, -τος (nom commun) (f)''' : Exultation. Emportement, férocité.<br>
'''γε (particule)''' : (Devient ''γ’'' devant un mot commençant par une voyelle.) Marque une restriction, une affirmation, ou une conclusion.<br>
'''γείτων, -ονος (nom commun) (m/f)''' : voisin(e).<br>
'''γέλαιμι (verbe)''' : Forme éolienne de ''γελάω''.<br>
'''γελάω (verbe)''' : Rire ; briller.<br>
'''γελοῖος, -ία, -ῖον (adjectif)''' : ridicule ; risible.<br>
'''γελοιότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''γελοῖος''.<br>
'''γελοιότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''γελοῖος''.<br>
'''γελοίως (adverbe)''' : ridiculement ; risiblement.<br>
'''γελόω (verbe)''' : Forme homérique de ''γελάω''.<br>
'''γέλως, -τος (nom commun) (m)''' : rire.<br>
'''γελωτοποιός, -ός, -όν (adjectif)''' : comique.<br>
'''γελωτοποιός, -οῦ (nom commun) (m)''' : Bouffon ; pitre.<br>
'''γεμίζω (verbe)''' : emplir.<br>
'''γέμισμα, -ίσματος (nom commun) (n)''' : emplissage.<br>
'''γενεά, -ᾶς (nom commun) (f)''' : naissance, genre ; espèce.<br>
'''γένεσις, -έσεως (nom commun) (f)''' : origine, source. Naissance. Création.<br>
'''γενετήσιος, -α, -ον (adjectif)''' : sexuel.<br>
'''γενετησιότατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''γενετήσιος''.<br>
'''γενετησιότερος, -έρα, -ερον (adjectif)''' : Comparatif de ''γενετήσιος''.<br>
'''γενετησίως (adverbe)''' : sexuellement.<br>
'''γενναῖος, -ία, -ῖον (adjectif)''' : vaillant.<br>
'''γενναιότατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''γενναῖος''.<br>
'''γενναιότερος, -έρα, -ερον (adjectif)''' : Comparatif de ''γενναῖος''.<br>
'''γενναίως (adverbe)''' : vaillamment.<br>
'''γενναιότης, -ητος (nom commun) (f)''' : vaillance.<br>
'''γεννητικός, -η, -ον (adjectif)''' : génital.<br>
'''γένος, -ους (nom commun) (n)''' : naissance, origine, descendance ; race, genre, espèce ; classe, corporation; nation, peuple, tribu.<br>
'''γεννῶ (verbe)''' : accoucher.<br>
'''γερόντειος, -α, -ον (adjectif)''' : .<br>
'''γεροντεύω (verbe)''' : .<br>'''γέρανος, -άνου (nom commun) (m/f)''' : grue (oiseau).<br>
'''γερουσία, -ας (nom commun) (f)''' : sénat.<br>
'''γερουσιάρχης, -ου (nom commun) (m)''' : président du sénat.<br>
'''γερουσιαστής, -οῦ (nom commun) (m)''' : sénateur.<br>
'''γέρων, -οντος (nom commun) (m)''' : vieillard.<br>
'''γεῦμα, -ύματος (nom commun) (n)''' : déjeuner.<br>
'''γεῦσις, -ύσεως (nom commun) (f)''' : goût.<br>
'''γεύω (verbe)''' : goûter.<br>
'''γέφυρα, -ύρας (nom commun) (f)''' : Chaussée. Pont.<br>
'''γεω- (préfixe)''' : relatif à la terre.<br>
'''γῆ, -ς (nom commun) (f)''' : terre.<br>
'''γήινος, -η, -ο (adjectif)''' : terrestre.<br>
'''γήρανσις, -άνσεως (nom commun) (f)''' : .<br>
'''γηράσκω (verbe)''' : .<br>
'''γῆρας, -ήρως (nom commun) (n)''' : vieillesse.<br>
'''γῆρυς, -ήρυος (nom commun) (f)''' : Voix ; discours.<br>
'''γηρύω (verbe)''' : chanter.<br>
'''γιγαντιαῖος, -ία, -ῖον (adjectif)''' : gigantesque.<br>
'''γιγαντιότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''γιγαντιαῖος''.<br>
'''γιγαντιότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''γιγαντιαῖος''.<br>
'''γιγαντίως (adverbe)''' : gigantesquement.<br>
''', -, - (adverbe)''' : Superlatif de ''γιγαντίως''.<br>
''', -, - (adverbe)''' : Comparatif de ''γιγαντίως''.<br>
'''γίγας, -αντος (nom commun) (m)''' : géant.<br>
'''γίγνομαι (verbe)''' : engendrer.<br>
'''γίνιουμαι (verbe)''' : Forme béotienne de ''γίγνομαι''.<br>
'''γίνομαι (verbe)''' : Forme ionienne de ''γίγνομαι''.<br>
'''γίνυμαι (verbe)''' : Forme thessalienne de ''γίγνομαι''.<br>
'''γιγνώσκω (verbe)''' : Apprendre à connaître.<br>
'''γινώσκω (verbe)''' : Forme ionienne de ''γιγνώσκω''.<br>
'''γλάγος, -ους (nom commun) (n)''' : Forme poétique de ''γάλα''.<br>
'''γλάσσα, -ης (nom commun) (f)''' : Forme ionienne de ''γλῶσσα''.<br>
'''γλαυκός, -ή, -όν (adjectif)''' : Brillant, étincelant, éclatant. D’un vert pâle ou gris.<br>
'''γλαυκότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''γλαυκός''.<br>
'''γλαυκότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''γλαυκός''.<br>
'''γλαυκῶς (adverbe)''' : vivement.<br>
'''γλαυκώτατα, -, - (adverbe)''' : Superlatif de ''γλαυκῶς''.<br>
'''γλαυκώτερον, -, - (adverbe)''' : Comparatif de ''γλαυκῶς''.<br>
'''γλαῦξ, -κός (nom commun) (f)''' : chouette.<br>
'''γλαύσσω (verbe)''' : briller (En parlant des yeux).<br>
'''γλεῦκος, -ύκους (nom commun) (n)''' : moût.<br>
'''γλέφαρον, -άρου (nom commun) (n)''' : Forme dorienne de ''βλέφαρον''.<br>
'''γλήνη, -ης (nom commun) (f)''' : pupille (partie de l’œil).<br>
'''γλῆνος, -ήνους (nom commun) (n)''' : splendeur.<br>
'''γλουτιαῖος, -ία, -αῖν (adjectif)''' : glutéal.<br>
'''γλουτός, -οῦ (nom commun) (m)''' : derrière ; fesse.<br>
'''γλυκέως (adverbe)''' : doucement.<br>
'''γλυκερός, -ή, -όν (adjectif)''' : doux.<br>
'''γλυκερῶς (adverbe)''' : doucement.<br>
'''γλυκύς, -εῖα, -ύ (adjectif)''' : doux, sucré.<br>
'''γλυκύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''γλυκύς''.<br>
'''γλυκύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''γλυκύς''.<br>
'''γλυκώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''γλυκερός''.<br>
'''γλυκώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''γλυκερός''.<br>
'''γλυπτόν, -οῦ (nom commun) (n)''' : .<br>
'''γλώνη, -ης (nom commun) (f)''' : poupée.<br>
'''γλῶσσα, -ώσσης (nom commun) (f)''' : langue. (Organe buccal ; expression orale)<br>
'''γλωσσίς, -δος (nom commun) (f)''' : glotte.<br>
'''γλῶττα, -ώττης (nom commun) (f)''' : Forme attique de ''γλῶσσα''.<br>
'''γλωττίς, -δος (nom commun) (f)''' : Forme attique de ''γλωσσίς''.<br>
'''γνάθος, -ου (nom commun) (f)''' : mâchoire.<br>
'''γνήσιος, -α, -ον (adjectif)''' : véritable.<br>
'''γνῶσις, -ώσεως (nom commun) (f)''' : Savoir ; connaissance, notion. Reconnaissance ; enquête, instruction judiciaire.<br>
'''γοάω (verbe)''' : se lamenter.<br>
'''γόγγρος, -ου (nom commun) (m)''' : congre.<br>
'''γόησσα, -ας (nom commun) (f)''' : enchanteresse ; magicienne.<br>
'''γόης, -τος (nom commun) (m)''' : enchanteur ; magicien.<br>
'''γονεύς, -έως (nom commun) (m)''' : père (parent).<br>
'''γονή, -ῆς (nom commun) (f)''' : génération ; procréation.<br>
'''γονόρροια, -ας (nom commun) (f)''' : gonorrhée.<br>
'''γόνος, -ου (nom commun) (m)''' : procréation.<br>
'''γόνυ, -ατος (nom commun) (n)''' : genou.<br>
'''γόος, -ου (nom commun) (m)''' : lamentation.<br>
'''γοργός, -ή, -όν (adjectif)''' : terrible.<br>
'''γούνα, -ας (nom commun) (f)''' : .<br>
'''γοῦνος, -ύνου (nom commun) (m)''' : Forme ionienne de ''γόνος''.<br>
'''γοῶ (verbe)''' : enchanter, ensorceler.<br>
'''γράθμα, -τος (nom commun) (n)''' : Forme dorienne de ''γράμμα''.<br>
'''γραῖα, -ίας (nom commun) (f)''' : vieillarde.<br>
'''γραμματεύς, -έως (nom commun) (m)''' : scribe.<br>
'''γράμμα, -τος (nom commun) (n)''' : Caractère gravé. Signes divers. Traits d’un dessin ou d’une peinture.<br>
'''γραπτός, -ή, -όν (adjectif)''' : écrit.<br>
'''γραπτότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''γραπτός''.<br>
'''γραπτότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''γραπτός''.<br>
'''γραπτῶς (adverbe)''' : .<br>
'''γραῦς, -ός (nom commun) (f)''' : vieillarde.<br>
'''γραφεύς, -έως (nom commun) (m)''' : peintre.<br>
'''γραφή, -ῆς (nom commun) (f)''' : peinture.<br>
'''γραφία, -ας (nom commun) (f)''' : écriture.<br>
'''-γραφία, -ας (suffixe)''' : relatif à l’écriture.<br>
'''γραφικός, -ή, -όν (adjectif)''' : peint.<br>
'''γραφικῶς (adverbe)''' : graphiquement.<br>
'''γραφικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''γραφικός''.<br>
'''γραφικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''γραφικός''.<br>
'''γραφίς, -δος (nom commun) (f)''' : .<br>
'''γράφω (verbe)''' : écrire.<br>
'''γρηγοράς, -δος (nom commun) (f)''' : vivacité.<br>
'''γρήγορος, -η, -ον (adjectif)''' : vif.<br>
'''γρηγόρως (adverbe)''' : vivement.<br>
'''γρηγορώτατα, -, - (adverbe)''' : Superlatif de ''γρηγόρως''.<br>
'''γρηγορώτερον, -, - (adverbe)''' : Comparatif de ''γρηγόρως''.<br>
'''γρηγορώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''γρήγορος''.<br>
'''γρηγορώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''γρήγορος''.<br>
'''γρῖφος, -ίφου (nom commun) (m)''' : filet ; énigme.<br>
'''γρόνθος, -ου (nom commun) (m)''' : poing.<br>
'''γρύλλος, -ου (nom commun) (m)''' : grillon.<br>
'''γρύψ, -πός (nom commun) (m)''' : griffon.<br>
'''γυμνάζω (verbe)''' : entraîner (diriger l’exercice sportif).<br>
'''γυμνάσιον, -ίου (nom commun) (n)''' : Lieu public réservé aux exercices corporels.<br>
'''γυμνός, -ή, -όν (adjectif)''' : Nu ; légèrement vêtu.<br>
'''γυμνότατα, -, - (adverbe)''' : Superlatif de ''γυμνῶς''.<br>
'''γυμνότερον, -, - (adverbe)''' : Comparatif de ''γυμνῶς''.<br>
'''γυμνότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''γυμνός''.<br>
'''γυμνότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''γυμνός''.<br>
'''γυμνότης, -τος (nom commun) (f)''' : nudité.<br>
'''γύμνωσις, -ώσεως (nom commun) (f)''' : dénudage.<br>
'''γυμνῶς (adverbe)''' : .<br>
'''γυμνῶ (verbe)''' : dénuder.<br>
'''γυνά, -ᾶς (nom commun) (f)''' : Forme dorienne de ''γυνή''.<br>
'''γυναικᾶς, - (nom commun) (m)''' : homme à femmes.<br>
'''γυναικεῖον, -ίου (nom commun) (n)''' : Appartement réservé aux femmes.<br>
'''γυναικεῖος, -ία, -ῖον (adjectif)''' : féminin.<br>
'''γυναικειότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''γυναικεῖος''.<br>
'''γυναικειότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''γυναικεῖος''.<br>
'''γυναικείως (adverbe)''' : fémininement.<br>
'''γυναικομανία, -ας (nom commun) (f)''' : gynécomanie.<br>
'''γυνή, -αικός (nom commun) (f)''' : Femme, épouse ; femelle des animaux.<br>
'''γυρῖνος, -ίνου (nom commun) (m)''' : têtard.<br>
'''γῦρος, -ύρου (nom commun) (m)''' : Anneau ; cercle.<br>
'''γυρός, -οῦ (nom commun) (m)''' : rond.<br>
'''γύψ, -πος (nom commun) (m)''' : vautour.<br>
'''γωνία, -ας (nom commun) (f)''' : Angle ; coin.<br>
'''γωρυτός, -οῦ (nom commun) (m)''' : carquois.<br>
'''Γαϐριήλ (nom propre) (m)''' : Gabriel.<br>
'''Γάζα, -ης (nom commun) (f)''' : Gaza.<br>
'''Γαῖα, -ίας (nom propre) (f)''' : [[wikt:Gaïa|Gaïa]].<br>
'''Γαῖη, -ίης (nom propre) (f)''' : Forme ionienne de ''Γαῖα''.<br>
'''Γαλινθιάς, -δος (nom propre) (f)''' : Galanthis.<br>
'''Γαλάτεια, -ίας (nom propre) (f)''' : Galatée (Néréide).<br>
'''Γαλατεία, -ας (nom propre) (f)''' : Galatée.<br>
'''Γαλάτης, -ου (nom commun) (m)''' : Galate.<br>
'''Γαλατία, -ας (nom propre) (f)''' : Galatie.<br>
'''Γαλλία, -ας (nom propre) (f)''' : Gaule.<br>
'''Γανυμήδης, -ου (nom propre) (m)''' : Ganymède.<br>
'''Γεδεών, -ος (nom propre) (m)''' : Gédéon.<br>
'''Γελλώ, -οῦς (nom propre) (f)''' : Gello.<br>
'''Γέλων, -ος (nom propre) (m)''' : Gélon.<br>
'''Γεννησαρέτ (nom propre) (f)''' : Gennésaret.<br>
'''Γερμανία, -ας (nom propre) (f)''' : Allemagne.<br>
'''Γερμανίς, -δος (nom commun) (f)''' : Allemande.<br>
'''Γερμανός, -οῦ (nom commun) (m)''' : Allemand.<br>
'''Γέτης, -ου (nom commun) (m)''' : Gète.<br>
'''Γεώργιος, -ίου (prénom) (m)''' : Georges.<br>
'''Γῆρας, -ήρως (nom propre) (m)''' : Géras.<br>
'''Γηρυόνης, -ου (nom propre) (m)''' : Forme alternative de ''Γηρυών''.<br>
'''Γηρυών, -όνος (nom propre) (m)''' : Géryon.<br>
'''Γῆ, -ς (nom propre) (f)''' : Terre.<br>
'''Γλαῦκος, -ύκου (nom propre) (m)''' : Glaucos.<br>
'''Γοργώ, -όνος (nom propre) (f)''' : Gorgone.<br>
'''Γότθος, -ου (nom commun) (m)''' : Goth.<br>
'''Γοῦτος, -ύτου (nom commun) (m)''' : Geat.<br>
'''Γραῖα, -ίας (nom propre) (f)''' : Grée.<br>
'''Γρηγόριος, -ίου (nom propre) (m)''' : Grégoire.<br>
'''Γύγης, -ου (nom propre) (m)''' : Gygès.<br>
'''Γύθειον, -ίου (nom propre) (n)''' : Gythio.<br>
'''Γύθιον, -ίου (nom propre) (n)''' : Forme alternative de ''Γύθειον''.<br>
==Δ==
'''δάγυς, -ύδος (nom commun) (f)''' : dagyde.<br>
'''δαήρ, -έρος (nom commun) (m)''' : beau-frère.<br>
'''δαιμόνιος, -ία, -όνιον (adjectif)''' : étrange.<br>
'''δαιμόνιον, -ίου (nom commun) (n)''' : génie (être merveilleux).<br>
'''δαιμονίως (adverbe)''' : étrangement.<br>
'''δαιμονίοτατα, -, - (adverbe)''' : Superlatif de ''δαιμονίως''.<br>
'''δαιμονίοτερον, -, - (adverbe)''' : Comparatif de ''δαιμονίως''.<br>
'''δαιμονιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''δαιμόνιος''.<br>
'''δαιμονιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''δαιμόνιος''.<br>
'''δαίμων, -ονος (nom commun) (m)''' : divinité.<br>
'''δακκύλιος, -υλίου (nom commun) (m)''' : Forme béotienne de ''δάκτυλος''.<br>
'''δάκνω (verbe)''' : mordre.<br>
'''δάκος, -ους (nom commun) (n)''' : .<br>
'''δάκρυ, -ύου (nom commun) (n)''' : larme.<br>
'''δακτύλιον, -ίου (nom commun) (n)''' : anneau.<br>
'''δάκτυλος, -ύλου (nom commun) (m)''' : doigt.<br>
'''δάλτος, -ου (nom commun) (f)''' : Forme chypriote de ''δέλτος''.<br>
'''δαμάζω (dompter)''' : domestiquer ; dompter.<br>
'''δάμαλις, -άλεως (nom commun) (f)''' : génisse.<br>
'''δαμοκρατία, -ας (nom commun) (f)''' : Forme dorienne de ''δημοκρατία''.<br>
'''δᾶμος, -άμου (nom commun) (m)''' : Forme dorienne de ''δῆμος''.<br>
'''δάνειον, -ίου (nom commun) (n)''' : prêt.<br>
'''δάνος, -ου (nom commun) (m)''' : Forme macédonienne de ''θάνατος''.<br>
'''δαρθάνω (verbe)''' : s’endormir.<br>
'''δάσος, -ους (nom commun) (n)''' : bois (lieu), forêt.<br>
'''δασύνω (verbe)''' : .<br>
'''δασύς, -εῖα, -ύ (adjectif)''' : Velu, poilu. feuillu ; boisé.<br>
'''δασύτης, -τος (nom commun) (f)''' : pilosité.<br>
'''δαῦκον, -ύκου (nom commun) (n)''' : carotte ou navet utilisé en médecine.<br>
'''δεῖγμα, -ίγματος (nom commun) (f)''' : échantillon.<br>
'''δείδω (verbe)''' : avoir peur.<br>
'''δείκτης, -ου (nom commun) (m)''' : index.<br>
'''δεινός, -ή, -όν (adjectif)''' : terrible.<br>
'''δέ (particule)''' : mais, puis, d’autre part, donc.<br>
'''δέκα (adjectif numéral)''' : dix.<br>
'''δεκαετία, -ας (nom commun) (f)''' : décennie.<br>
'''δεκάς, -δος (nom commun) (f)''' : dizaine.<br>
'''δέκομαι (verbe)''' : Forme éolienne et ionienne de ''δέχομαι''.<br>
'''δελεάζω (verbe)''' : appâter.<br>
'''δέλεαρ, -τος (nom commun) (n)''' : appât.<br>
'''δέλτα (nom commun) (n)''' : delta.<br>
'''δέλτος, -ου (nom commun) (f)''' : tablette d’écriture.<br>
'''δέλφαξ, -κος (nom commun) (f)''' : .<br>
'''δελφίς, -ῖνος (nom commun) (m)''' : dauphin.<br>
'''δελφύς, -ος (nom commun) (f)''' : matrice.<br>
'''δέμω (verbe)''' : construire.<br>
'''δενδρολίϐανον, -άνου (nom commun) (n)''' : romarin.<br>
'''δένδρον, -ου (nom commun) (n)''' : arbre.<br>
'''δένδρεον, -ου (nom commun) (n)''' : Forme homérique de ''δένδρον''.<br>
'''δεξιτερός, -ή, -όν (adjectif)''' : Forme de ''δεξιός''.<br>
'''δεξιός, -ά, -όν (adjectif)''' : qui est à droite, placé à droite. (Par suite) De bon augure, favorable. Qui a de la dextérité, adroit, industrieux, habile.<br>
'''δέος, -ους (nom commun) (n)''' : effroi, peur.<br>
'''δέρας, -ατος (nom commun) (n)''' : peau, cuir.<br>
'''δέρκομαι (verbe)''' : voir clair.<br>
'''δέρμα, -τος (nom commun) (n)''' : peau.<br>
'''δέρω (verbe)''' : écorcher.<br>
'''δέσμευσις, -ύσεως (nom commun) (f)''' : lien.<br>
'''δεσμεύω (verbe)''' : lier.<br>
'''δέσμη, -ης (nom commun) (f)''' : bouquet, faisceau, liasse.
'''δεσμός, -οῦ (nom commun) (m)''' : Lien (corde, câble, amarre, courroie, nœud). (Par extension) Clou. (D’ordinaire au pluriel) Liens, chaînes, fers. (Par suite) Emprisonnement, prison. (En général) Captivité. (Figuré) Liens d’amitié.<br>
'''δέσποινα, -ίνης (nom commun) (f)''' : maîtresse d’une maisonnée.<br>
'''δεσπότης, -ου (nom commun) (m)''' : maître d’une maisonnée ; maître d’un dème.<br>
'''δεῦμα, -ύματος (nom commun) (n)''' : chair cuite.<br>
'''δεῦρο (adverbe)''' : ici (avec mouvement).<br>
'''δευτεραγωνιστής, -οῦ (nom commun) (m)''' : deutéragoniste.<br>
'''δεύτερος, -α, -ον (adjectif)''' : deuxième.<br>
'''δευτήρ, -ῆρος (nom commun) (m)''' : chaudron.<br>
'''δεύω (verbe)''' : mouiller.<br>
'''δέφυρα, -ύρας (nom commun) (f)''' : Forme crétoise de ''γέφυρα''.<br>
'''δέχομαι (verbe)''' : Accepter, admettre, agréer. Accueillir, recevoir, recueillir, adopter. Prendre, revêtir, comporter.<br>
'''δή (particule)''' : vraiment, assurément.<br>
'''δῆγμα, -ήγματος (nom commun) (m)''' : morsure.<br>
'''δηκτήριος, -ος, -ον (adjectif)''' : mordant.<br>
'''δηλητήρ, -ρός (nom commun) (m)''' : destructeur.<br>
'''δηλοῦμαι (verbe)''' : détruire.<br>
'''δήλωσις, -ώσεως (nom commun) (f)''' : déclaration.<br>
'''δηλῶ (verbe)''' : déclarer.<br>
'''δημοκρατία, -ας (nom commun) (f)''' : république.<br>
'''δημόσιος, -ία, -όσιον (adjectif)''' : public.<br>
'''δημοσίως (adverbe)''' : publiquement.<br>
'''δημοσιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''δημόσιος''.<br>
'''δημοσιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''δημόσιος''.<br>
'''δῆμος, -ήμου (nom commun) (m)''' : contrée, pays, terre.<br>
'''δημότης, -ου (nom commun) (m)''' : concitoyen.<br>
'''δημοτικός, -ή, -όν (adjectif)''' : commun.<br>
'''δημοτικῶς (adverbe)''' : communément.<br>
'''δημοτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''δημοτικός''.<br>
'''δημοτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''δημοτικός''.<br>
'''δημοτικώτατα, -, - (adverbe)''' : Superlatif de ''δημοτικῶς''.<br>
'''δημοτικώτερον, -, - (adverbe)''' : Comparatif de ''δημοτικῶς''.<br>
'''δημωδέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''δημώδης''.<br>
'''δημωδέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''δημώδης''.<br>
'''δημώδης, -ης, -ες (adjectif)''' : vernaculaire.<br>
'''δημωδῶς (adverbe)''' : vernaculairement.<br>
'''δήν (particule)''' : il y a longtemps.<br>
'''δηρός, -ά, -όν (adjectif)''' : .<br>
'''διά (adverbe ; préposition)''' : .<br>
'''διαϐεϐαιῶ (verbe)''' : garantir (se rendre garant de l’existence de la réalité d’une chose).<br>
'''διαϐιϐάζω (verbe)''' : lire.<br>
'''διαϐιϐρώσκω (verbe)''' : .<br>
'''διάϐολος, -ου (masculin) (m)''' : calomniateur ; diable.<br>
'''διαϐολικός, -ή, -όν (adjectif)''' : du diable.<br>
'''διαϐολικῶς (adverbe)''' : diaboliquement.<br>
'''διαϐολικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''διαϐολικός''.<br>
'''διαϐολικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''διαϐολικός''.<br>
'''διάγνωσις, -ώσεως (nom commun) (f)''' : .<br>
'''διάδημα, -ήματος (nom commun) (n)''' : diadème.<br>
'''διαδῶ (verbe)''' : .<br>
'''διάζωμα, -ώματος (nom commun) (n)''' : caleçon.<br>
'''διάθεσις, -έσεως (nom commun) (f)''' : disposition.<br>
'''διαθήκη, -ης (nom commun) (f)''' : testament, volonté (document écrit). Testament (livre religieux)<br>
'''διαίρεσις, -έσεως (nom commun) (f)''' : division.<br>
'''διαίσθησις, -ήσεως (nom commun) (f)''' : intuition.<br>
'''διαισθητικός, -ή, -όν (adjectif)''' : intuitif.<br>
'''διαισθητικῶς (adverbe)''' : intuitivement.<br>
'''διαιτητής, -οῦ (nom commun) (m)''' : arbitre.<br>
'''διακινῶ (verbe)''' : .<br>
'''διάκονος, -όνου (nom commun) (m/f)''' : Serviteur, servante.<br>
'''διακοπή, -ῆς (nom commun) (f)''' : interruption.<br>
'''διακόπτω (verbe)''' : interrompre.<br>
'''διακορεύω (verbe)''' : déflorer.<br>
'''διακόρησις, -ήσεως (nom commun) (f)''' : défloration.<br>
'''διάκρισις, -ίσεως (nom commun) (f)''' : discrétion ; distinction.<br>
'''διακριτικός, -ή, -όν (adjectif)''' : discret ; distinct.<br>
'''διακριτικότατα, -, - (adverbe)''' : Superlatif de ''διακριτικῶς''.<br>
'''διακριτικότερον, -, - (adverbe)''' : Comparatif de ''διακριτικῶς''.<br>
'''διακριτικότατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''διακριτικός''.<br>
'''διακριτικότερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''διακριτικός''.<br>
'''διακριτικῶς (adverbe)''' : discrètement ; distinctement.<br>
'''διάλεξις, -έξεως (nom commun) (f)''' : exposé.<br>
'''διαλέγομαι (verbe)''' : discuter.<br>
'''διάλεκτος, -έκτου (nom commun) (f)''' : discussion ; dialecte.<br>
'''διαλλάττω (verbe)''' : transiger.<br>
'''διάλογος, -όγου (nom commun) (m)''' : dialogue.<br>
'''διαμαστίγωσις, -ώσεως (nom commun) (f)''' : .<br>
'''διαμαστιγῶ (verbe)''' : .<br>
'''διανόησις, -ήσεως (nom commun) (f)''' : pensée.<br>
'''διανοῶ (verbe)''' : penser.<br>
'''διαπραγμάτευσις, -ύσεως (nom commun) (f)''' : négociation.<br>
'''διαπραγματεύομαι (verbe)''' : négocier.<br>
'''διαρρήγνυμι (verbe)''' : cambrioler.<br>
'''διαρρήκτης, -ου (nom commun) (m)''' : cambrioleur.<br>
'''διάρρηξις, -ήξεως (nom commun) (m)''' : cambriolage.<br>
'''διάρροια, -ας (nom commun) (f)''' : diarrhée.<br>
'''διάσεισις, -ίσεως (nom commun) (f)''' : commotion.<br>
'''διασείω (verbe)''' : .<br>
'''διασκέδασις, -άσεως (nom commun) (f)''' : divertissement.<br>
'''διάστασις, -άσεως (nom commun) (f)''' : dimension.<br>
'''διαστέλλω (verbe)''' : Répandre, séparer. Distinguer, déterminer.<br>
'''διάστημα, -ήματος (nom commun) (n)''' : espace.<br>
'''διαστολή, -ῆς (nom commun) (f)''' : Élargissement, expansion, dilatation. Petite coche ou fente.<br>
(Figuré) Distinction.<br>
'''διάστρεμμα, -έμματος (nom commun) (n)''' : entorse.<br>
'''διαστρέφω (verbe)''' : disposer.<br>
'''διατίθημι (verbe)''' : disposer.<br>
'''διατριϐή, -ῆς (nom commun) (f)''' : conversation (philosophique).<br>
'''διαφθείρω (verbe)''' : corrompre.<br>
'''διαφθορά, -ᾶς (nom commun) (f)''' : corruption.<br>
'''διαφθορεῖον, -ίου (nom commun) (n)''' : .<br>
'''διαφθορεύς, -έως (nom commun) (m)''' : .<br>
'''διαχειριστής, -οῦ (masculin) (m)''' : gestionnaire.<br>
'''διαχείρισις, -ίσεως (nom commun) (f)''' : gestion.<br>
'''διαχειριστικός, -ή, -όν (adjectif)''' : .<br>
'''διαχειριστικῶς (adverbe)''' : -ment.<br>
'''διαχειριστικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''διαχειριστικός''.<br>
'''διαχειριστικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''διαχειριστικός''.<br>
'''διαχειριστικώτατα, -, - (adverbe)''' : Superlatif de ''διαχειριστικῶς''.<br>
'''διαχειριστικώτερον, -, - (adverbe)''' : Comparatif de ''διαχειριστικῶς''.<br>
'''διδάσκαλος, -άλου (nom commun) (m)''' : instituteur.<br>
'''διδάσκω (verbe)''' : enseigner, instruire ; entraîner.<br>
'''διαφέρω (verbe)''' : différer.<br>
'''διαφορά, -ᾶς (nom commun) (f)''' : différence.<br>
'''διαφορετικός, -ή, -όν (adjectif)''' : différent.<br>
'''διαφορετικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''διαφορετικός''.<br>
'''διαφορετικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''διαφορετικός''.<br>
'''διαφορετικώτατα, -, - (adverbe)''' : Superlatif de ''διαφορετικῶς''.<br>
'''διαφορετικώτερον, -, - (adverbe)''' : Comparatif de ''διαφορετικῶς''.<br>
'''δίδυμος, -ος, -ον (adjectif)''' : double ; jumeau.<br>
'''δίδωμι (verbe)''' : donner.<br>
'''διείσδυσις, -ύσεως (nom commun) (f)''' : pénétration.<br>
'''διεισδύω (verbe)''' : pénétrer.<br>
'''διένεξις, -έξεως (nom commun) (f)''' : contentieux.<br>
'''διεύθυνσις, -ύνσεως (nom commun) (f)''' : direction.<br>
'''διευθύνων, -ουσα, -ον (adjectif)''' : directeur.<br>
'''διευθυντής, -οῦ (nom commun) (m)''' : directeur.<br>
'''διευθύνω (verbe)''' : diriger.<br>
'''διθύραμϐος, -άμϐου (nom commun) (m)''' : dithyrambe.<br>
'''διήγημα, -ήματος (nom commun) (n)''' : conte.<br>
'''διήγησις, -ήσεως (nom commun) (f)''' : narration.<br>
'''διηγοῦμαι (verbe)''' : narrer.<br>
'''διήκονος, -όνου (nom commun) (m/f)''' : Forme ionienne de ''διάκονος''.<br>
'''διίσταμαι (verbe)''' : .<br>
'''διίστημι (verbe)''' : .<br>
'''δικάζω (verbe)''' : juger.<br>
'''δικαίωμα, -ώματος (nom commun) (n)''' : (Droit) Jugement. Justification. Décret.<br>
'''δικαιῶ (verbe)''' : rendre juste.<br>
'''δικαστήριον, -ίου (nom commun) (n)''' : tribunal.<br>
'''δικαστής, -οῦ (nom commun) (m)''' : juge.<br>
'''δίκη, -ης (nom commun) (f)''' : Coutume, manière, mode. Ordre, loi, droit. Justice. Jugement. Punition, vengeance, pénalité.<br>
'''δίκτυον, -ύου (nom commun) (n)''' : filet.<br>
'''δίνη, -ης (nom commun) (f)''' : tourbillon.<br>
'''δίλημμα, -ήμματος (nom commun) (n)''' : dilemme.<br>
'''διπλοῦς, -ῆ, -οῦν (adjectif)''' : double.<br>
'''διοίκησις, -ήσεως (nom commun) (f)''' : administration.<br>
'''διοικητής, -οῦ (nom commun) (m)''' : administrateur.<br>
'''διοικητικός, -ή, -όν (adjectif)''' : administratif.<br>
'''διοικῶ (verbe)''' : administrer.<br>
'''διορθώνω (verbe)''' : corriger.<br>
'''διουρητικόν, -οῦ (nom commun) (n)''' : diurétique.<br>
'''διουρητικός, -ή, -όν (adjectif)''' : diurétique.<br>
'''δισσός, -ή, -όν (adjectif)''' : double.<br>
'''δισσότατα, -, - (adverbe)''' : Superlatif de ''δισσῶς''.<br>
'''δισσότερον, -, - (adverbe)''' : Comparatif de ''δισσῶς''.<br>
'''δισσότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''δισσός''.<br>
'''δισσότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''δισσός''.<br>
'''δίς (adverbe)''' : deux fois.<br>
'''δισσῶς (adverbe)''' : doublement.<br>
'''δίφθογγος, -όγγου (nom commun) (f)''' : diphtongue.<br>
'''δίφουρα, -ύρας (nom commun) (f)''' : Forme laconienne de ''γέφυρα''.<br>
'''δίφρος, -ου (nom commun) (m)''' : tabouret.<br>
'''διχόνοια, -ας (nom commun) (f)''' : discorde.<br>
'''διχορεῖος, -ίου (nom commun) (m)''' : dichorée.<br>
'''δίψα, -ης (nom commun) (f)''' : soif.<br>
'''διψώ (verbe)''' : avoir soif.<br>
'''διωγμός, -οῦ (nom commun) (m)''' : persécution.<br>
'''διώκω (verbe)''' : persécuter.<br>
'''διῶρυξ, -ώρυγος (nom commun) (f)''' : canal.<br>
'''δμωή, -ῆς (nom commun) (f)''' : domestique, servante.<br>
'''δμῳή, -ῆς (nom commun) (f)''' : esclave.<br>
'''δμῴιος, -ίου (nom commun) (m)''' : esclave.<br>
'''δμώς, -ός (nom commun) (m)''' : domestique, serviteur.<br>
'''δόγμα, -τος (nom commun) (n)''' : Opinion. Décision, décret, arrêt. Doctrine.<br>
'''δογματικός, -ή, -όν (adjectif)''' : doctrinal.<br>
'''δογματικῶς (adverbe)''' : doctrinalement.<br>
'''δογματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''δογματικός''.<br>
'''δογματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''δογματικός''.<br>
'''δοθιήν, -ένος (nom commun) (m)''' : furoncle.<br>
'''δόκησις, -ήσεως (nom commun) (f)''' : Opinion ; croyance.<br>
'''δοκέω (verbe)''' : Penser, supposer. Sembler.<br>
'''δολερός, -ή, -όν (adjectif)''' : rusé.<br>
'''δολιόω (verbe)''' : abuser.<br>
'''δόλος, -ου (nom commun) (m)''' : ruse.<br>
'''δόξα, -ης (nom commun) (f)''' : Opinion, vue, point de vue, conjecture, supposition. (Dans le Nouveau Testament.) Gloire, honneur ; splendeur.<br>
'''δοξάζω (verbe)''' : Imaginer. Glorifier.<br>
'''δοξολογία, -ας (nom commun) (f)''' : doxologie.<br>
'''δοξόλογος, -όγου (nom commun) (m)''' : doxologue.<br>
'''δοξοσοφία, -ας (nom commun) (f)''' : doxosophie.<br>
'''δοξόσοφος, -όφου (nom commun) (m)''' : doxosophe.<br>
'''δουλεία, -ας (nom commun) (f)''' : servitude.<br>
'''δουλεύω (verbe)''' : être esclave. Travailler à gages, faire un travail mercenaire.<br>
'''δούλη, -ης (nom commun) (f)''' : esclave.<br>
'''δοῦλος, -ύλου (nom commun) (m)''' : esclave.<br>
'''δουλόω (verbe)''' : asservir.<br>
'''δούξ, -κός (nom commun) (m)''' : chef.<br>
'''δόμος, -ου (nom commun) (m)''' : Maison, palais ; chambre, appartement.<br>
'''δομή, -ῆς (nom commun) (f)''' : structure.<br>
'''δόμησις, -ήσεως (nom commun) (f)''' : construction.<br>
'''δομικός, -ή, -όν (suffixe)''' : structurel.<br>
'''δομικῶς (adverbe)''' : structurellement.<br>
'''δομικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''δομικός''.<br>
'''δομικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''δομικός''.<br>
'''δομικώτατα, -, - (adverbe)''' : Superlatif de ''δομικῶς''.<br>
'''δομικώτερον, -, - (adverbe)''' : Comparatif de ''δομικῶς''.<br>
'''δόναξ, -κος (nom commun) (m)''' : Roseau ; objet fait de roseau.<br>
'''δονέω (verbe)''' : agiter.<br>
'''δόνημα, -ήματος (nom commun) (n)''' : agitation.<br>
'''δορκάς, -δος (f)''' : chevreuil.<br>
'''δοράκινον, -ίνου (nom commun) (n)''' : pêche (fruit).<br>
'''δόρκος, -ου (nom commun) (m)''' : Forme de ''δορκάς''.<br>
'''δόρκων, -ος (nom commun) (m)''' : Forme de ''δορκάς''.<br>
'''δόρξ, -κος (nom commun) (m)''' : Forme de ''δορκάς''.<br>
'''δόσις, -εως (nom commun) (f)''' : don ; cadeau.<br>
'''δοχεῖον, -ίου (nom commun) (n)''' : récipient.<br>
'''δράκαινα, -ίνης (nom commun) (f)''' : dracène.<br>
'''δράκων, -οντος (nom commun) (m)''' : dragon.<br>
'''δρᾶμα, -άματος (nom commun) (n)''' : action théâtrale ; pièce de théâtre.<br>
'''δραπέτης, -ου (nom commun) (m)''' : fugitif.<br>
'''δράσσομαι (verbe)''' : saisir.<br>
'''δραχμή, -ῆς (nom commun) (f)''' : Poignée, contenu de la main. Drachme attique.<br>
'''δράω (verbe)''' : .<br>
'''δρίλαξ, -κος (nom commun) (m)''' : sangsue.<br>
'''δρομαῖος, -ία, -ῖον (adjectif)''' : .<br>
'''δρομάς, -δος (nom commun) (m)''' : dromadaire.<br>
'''δρόμος, -ου (nom commun) (m)''' : Course (de chevaux), lutte à la course, tour de promenade.<br>
'''δρόσος, -ου (nom commun) (f)''' : rosée.<br>
'''δρυμός, -οῦ (nom commun) (m)''' : bois (lieu), forêt.<br>
'''δύη, -ης (nom commun) (f)''' : misère.<br>
'''δύναμις, -εως (nom commun) (f)''' : force en puissance.<br>
'''δύο (adjectif numéral)''' : deux.<br>
'''δύσις, -εως (nom commun) (f)''' : ouest.<br>
'''δυσλογία, -ας (nom commun) (f)''' : .<br>
'''δυσμικός, -ή, -όν (adjectif)''' : occidental.<br>
'''δυσ- (préfixe)''' : Difficulté, malheur.<br>
'''δυσσέϐεια, -ίας (nom commun) (f)''' : impiété.<br>
'''δυσσεϐέστατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''δυσσεϐής''.<br>
'''δυσσεϐέστερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''δυσσεϐής''.<br>
'''δυσσεϐής, -ής, -ές (adjectif)''' : impie.<br>
'''δυσσεϐῶς (adverbe) : impieusement.<br>
'''δυστύχημα, -ήματος (nom commun) (n)''' : accident.<br>
'''δυστυχής, -ής, -ές (adjectif)''' : malheureux.<br>
'''δυστυχία, -ας (nom commun) (f)''' : malheur.<br>
'''δυστυχῶς (adverbe)''' : malheureusement.<br>
'''δυστυχῶ (verbe)''' : causer un malheur.<br>
'''δυτικός, -ή, -όν (adjectif)''' : occidental.<br>
'''δυτικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''δυτικός''.<br>
'''δυτικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''δυτικός''.<br>
'''δυτικότατα, -, - (adverbe)''' : Superlatif de ''δυτικῶς''.<br>
'''δυτικότερον, -, - (adverbe)''' : Comparatif de ''δυτικῶς''.<br>
'''δυτικῶς (adverbe)''' : occidentalement.<br>
'''δύω (verbe)''' : S’enfoncer, se plonger. (Par extension) Pénétrer dans. (Par analogie) Se revêtir de.<br>
'''δώδεκα (adjectif numéral)''' : douze.<br>
'''δωδεκάς, -δος (nom commun) (f)''' : douzaine.<br>
'''δῶμα, -ώματος (nom commun) (n)''' : Construction. Maison, demeure. Chambre principale. Temple (demeure d’un dieu).<br>
'''δωμάτιον, -ίου (nom commun) (n)''' : chambre.<br>
'''δωράκινον, -ίνου (nom commun) (n)''' : pêche (fruit).<br>
'''δωρικός, -ή, -όν (adjectif)''' : dorien.<br>
'''δωροδοκῶ (verbe)''' : corrompre, suborner.<br>
'''δῶρον, -ώρου (nom commun) (n)''' : Don ; présent. Paume de la main ; palme.<br>
'''δώτωρ, -ορος (nom commun) (m)''' : donneur.<br>
'''δῶ (verbe)''' : Lier, attacher. (Par extension) Enfermer, emprisonner. (Par analogie) Entraver, empêcher, retenir. (Figuré) Lier, enchaîner. Manquer, avoir besoin de. (Impersonnel) Il est besoin de, il faut. (Forme moyenne) Avoir besoin.
(Par suite) Demander, prier.<br>
'''Δαϐίδ (nom propre) (m)''' : Forme alternative de ''Δαυίδ''.<br>
'''Δαίδαλος, -άλου (nom propre) (m)''' : Dédale.<br>
'''Δάκης, -ου (nom commun) (n)''' : Dace.<br>
'''Δαμασκός, -οῦ (nom propre) (f)''' : Damas.<br>
'''Δαμαστής, -οῦ (nom propre) (m)''' : Damastès (Autre surnom de Polypémon).<br>
'''Δαμάτηρ, -τρός (nom propre) (f)''' : Forme arcado-chypriote, béotienne, et dorienne de ''Δημήτηρ''.<br>
'''Δαμιανός, -οῦ (nom propre) (m)''' : Damien.<br>
'''Δαμία, -ας (nom propre) (f)''' : Damia.<br>
'''Δάμις, - (nom propre) (m)''' : Damis.<br>
'''Δάν, -ός (nom propre) (m)''' : Forme de ''Ζεύς''.<br>
'''Δανιήλ (nom propre) (m)''' : Daniel.<br>
'''Δάνος, -ου (nom commun) (m)''' : Forme macédonienne de ''Θάνατος''.<br>
'''Δαυείδ (nom propre) (m)''' : Forme alternative de ''Δαυίδ''.<br>
'''Δαυίδ (nom propre) (m)''' : David.<br>
'''Δεῖμος, -ίμου (nom propre) (m)''' : Déimos.<br>
'''Δεινώ, -οῦς (nom propre) (f)''' : Dino. (une des Grées)<br>
'''Δέσποινα, -ίνης (nom propre) (f)''' : [[wikt:Despina|Despina]].<br>
'''Δεύς, -ιός (nom propre) (m)''' : Forme laconienne de ''Ζεύς''.<br>
'''Δῃάνειρα, -ίρας (nom propre) (f)''' : Déjanire.<br>
'''Δηϊδάμεια, -ίας (nom propre) (f)''' : Déidamie.<br>
'''Δηιόκης, -ου (nom propre) (m)''' : Déjocès.<br>
'''Δηΐφοϐος, -όϐου (nom propre) (m)''' : Déiphobe.<br>
'''Δημήτηρ, -τρος (nom propre) (f)''' : [[wikt:Déméter|Déméter]].<br>
'''Δημήτριος, -ίου (nom propre) (m)''' : Démétrios.<br>
'''Δημιουργός, -οῦ (nom propre) (m)''' : Créateur.<br>
'''Δημοσθένης, -ους (nom propre) (m)''' : Démosthène.<br>
'''Δημώναξ, -ώνακτος (nom propre) (m)''' : Démonax.<br>
'''Δίδυμοι, -ων (nom propre) (m)''' : Gémeaux.<br>
'''Δίκη, -ης (nom propre) (f)''' : [[wikt:Dicé|Dicé]]. (Déesse de la justice divine.)<br>
'''Δίκτυον, -ύου (nom propre) (n)''' : Réticule.<br>
'''Διόδωρος, -ώρου (nom propre) (m)''' : Diodore.<br>
'''Διογένης, -ους (nom propre) (m)''' : Diogène.<br>
'''Διόνυσος, -ύσου (nom propre) (m)''' : [[wikt:Dionysos|Dionysos]].<br>
'''Διομέδων, -οντος (nom propre) (m)''' : Diomède.<br>
'''Δίς, -ιός (nom propre) (m)''' : Forme rare de ''Ζεύς''.<br>
'''Διώνη, -ης (nom propre) (f)''' : [[wikt:Dioné|Dioné]].<br>
'''Δούναϐις, -άϐεως (nom propre) (m)''' : Danube.<br>
'''Δύμη, -ης (nom propre) (f)''' : Dymé.<br>
'''Δυσνομία, -ας (nom propre) (m)''' : Dysnomie.<br>
'''Δωμάτηρ, -τρός (nom propre) (f)''' : Forme éolienne de ''Δημήτηρ''.<br>
'''Δωριεύς, -έως (nom commun) (m)''' : Dorien.<br>
'''Δωρίς, -δος (nom propre) (f)''' : Doris. (Océanide) (nom commun) Dorienne.<br>
'''Δωροθέα, -ας (nom propre) (f)''' : Dorothée.<br>
'''Δωρόθεος, -έου (nom propre) (m)''' : Dorothéos.<br>
'''Δῶρος, -ώρου (nom propre) (m)''' : Doros.<br>
==Ε==
'''ἐάν (conjonction)''' : si (éventuel).<br>
'''ἔαρ, -ος (nom commun) (n)''' : printemps.<br>
'''ἑϐδομάς, -δος (nom commun) (f)''' : semaine.<br>
'''ἑϐδομήκοντα (adjectif numéral)''' : soixante-dix.<br>
'''ἕϐδομος, -όμη, -ομον (adjectif numéral)''' : septième.<br>
'''ἑϐραΐζω (verbe)''' : .<br>
'''ἑϐραῖος, -ία, -ῖον (adjectif)''' : israélite.<br>
'''ἑϐραϊκός, -ή, -όν (adjectif)''' : hébraïque.<br>
'''ἑϐραϊστί (adverbe)''' : en hébreu.<br>
'''ἐγγόνη, -ης (nom commun) (f)''' : petite-fille.<br>
'''ἐγγύησις, -ήσεως (nom commun) (f)''' : garantie.<br>
'''ἐγγύς (adverbe)''' : proche.<br>
'''ἐγγυῶμαι (verbe)''' : garantir (se rendre garant, répondre d’une chose, du maintien, de l’exécution d’une chose).<br>
'''ἐγείρω (verbe)''' : réveiller.<br>
'''ἐγκέφαλος, -άλου (nom commun) (m)''' : cerveau.<br>
'''ἔγκλησις, -ίσεως (nom commun) (f)''' : accusation.<br>
'''ἔγκλισις, -ίσεως (nom commun) (f)''' : mode (grammaire).<br>
'''ἐγκόπρησις, -ήσεως (nom commun) (f)''' : incontinence fécale.<br>
'''ἐγκόσμιος, -α, -ο (adjectif)''' : .<br>
'''ἔγκυος, -ος, -ον (adjectif)''' : enceinte.<br>
'''ἐγκυμονῶ (verbe)''' : engrossir.<br>
'''ἐγκύμων, -ων, -ον (adjectif)''' : .<br>
'''ἐγχειρίδιον, -ίου (nom commun) (n)''' : manuel ; poignard.<br>
'''ἐγώ (pronom personnel)''' : je.<br>
'''ἔδαφος, -άφους (nom commun) (n)''' : sol.<br>
'''ἐδεμικός, -ή, -όν (adjectif)''' : édénique.<br>
'''ἐδεμικῶς (adverbe)''' : édéniquement.<br>
'''ἐδεμικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐδεμικός''.<br>
'''ἐδεμικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐδεμικός''.<br>
'''ἐδεμικώτατα, -, - (adverbe)''' : Superlatif de ''ἐδεμικῶς''.<br>
'''ἐδεμικώτερον, -, - (adverbe)''' : Comparatif de ''ἐδεμικῶς''.<br>
'''ἔδεσμα, -έσματος (nom commun) (n)''' : nourriture.<br>
'''ἕδρα, -ας (nom commun) (f)''' : Siège. Trône. Résidence, demeure. Partie du corps sur laquelle on s'assied. Action de s'asseoir. Assemblée siégeante.<br>
'''ἑδραῖος, ία, -ῖον (adjectif)''' : .<br>
'''ἐδώδιμος, -η, -ο (adjectif)''' : mangeable.<br>
'''ἐδωδή, -ῆς (nom commun) (f)''' : .<br>
'''ἔδω (verbe)''' : nourrir.<br>
'''ἕζομαι (verbe)''' : asseoir.<br>
'''ἔζω (verbe)''' : nourrir.<br>
'''ἐθίζω (verbe)''' : accoutumer.<br>
'''ἐθικός, -ή, -όν (adjectif)''' : coutumier.<br>
'''ἐθικῶς (adverbe)''' : coutumièrement.<br>
'''ἐθικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐθικός''.<br>
'''ἐθικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐθικός''.<br>
'''ἐθνικός, -ή, -όν (adjectif)''' : national.<br>
'''ἐθνικῶς (adverbe)''' : sagement.<br>
'''ἐθνικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐθνικός''.<br>
'''ἐθνικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐθνικός''.<br>
'''ἔθνος, -ους (nom commun) (n)''' : Famille, ensemble des proches. Nation. Tribu. (Religion) (Au pluriel) Gentils, les non-juifs, usage tardif dans la Bible. Troupeau.<br>
'''ἔθος, -ους (nom commun) (n)''' : coutume.<br>
'''ἔθω (verbe)''' : avoir coutume de ; être habituel.<br>
'''-ειδής, -ής, -ές (suffixe)''' : en forme de.<br>
'''-ειδῶς (suffixe)''' : .<br>
'''-ειδέστατος, -άτη, -έστατον (suffixe)''' : Superlatif de ''-ειδής''.<br>
'''-ειδέστερος, -έρα, -έστερον (suffixe)''' : Comparatif de ''-ειδής''.<br>
'''εἰδοποίησις, -ήσεως (nom commun) (f)''' : information.<br>
'''εἰδοποιῶ (verbe)''' : informer.<br>
'''εἶδος, -ἴδους (nom commun) (f)''' : Forme du corps ; air d'une personne ou d'une chose.<br>
'''εἰδύλλιον, -ίου (nom commun) (n)''' : Petit poème lyrique.<br>
'''εἰδωλολάτρης, -ου (nom commun) (m)''' : païen.<br>
'''εἰδωλολατρία, -ας (nom commun) (f)''' : paganisme.<br>
'''εἴδωλον, -ώλου (nom commun) (n)''' : Simulacre, fantôme ; image, portrait.<br>
'''εἴδω (verbe)''' : voir.<br>
'''εἰ (adverbe, conjonction)''' : si.<br>
'''εἰ μή (conjonction)''' : à moins que ; sauf si.<br>
'''εἰκονικός, -ή, -όν (adjectif)''' : virtuel.<br>
'''εἰκός, -τος (nom commun) (n)''' : raison.<br>
'''εἰκών, -όνος (nom commun) (f)''' : image, portrait.<br>
'''εἴκω (verbe)''' : Être semblable ; ressembler.<br>
'''εἰσαφίημι (verbe)''' : .<br>
'''εἰς (adverbe ; préposition)''' : Dans. Jusqu’à ; vers.<br>
'''εἷς, ἑνός (adjectif numéral)''' : un.<br>
'''εἰκοσαριά, -ᾶς (nom commun) (f)''' : vingtaine.<br>
'''εἴκοσι(ν) (adjectif numéral)''' : vingt.<br>
'''ἐείκοσι(ν) (adjectif numéral)''' : Forme homérique de ''εἴκοσι(ν)''.<br>
'''ϝίκατι(ν) (adjectif numéral)''' : Forme béotienne de ''εἴκοσι(ν)''.<br>
'''ϝείκατι(ν) (adjectif numéral)''' : Forme dorienne de ''εἴκοσι(ν)''.<br>
'''βείκατι(ν) (adjectif numéral)''' : Forme sud-orientale de ''εἴκοσι(ν)''.<br>
'''εἷλιξ, -κος (nom commun) (f)''' : Forme poétique de ''ἕλιξ''.<br>
'''εἰλύω (verbe)''' : .<br>
'''εἴλω (verbe)''' : tourner ; enrouler. Entasser.<br>
'''εἷμα, -ἵματος (nom commun) (n)''' : Couverture ; vêtement.<br>
'''εἶμι (verbe)''' : aller, se déplacer.<br>
'''εἰμί (verbe)''' : être.<br>
'''-εῖον, -ίου (suffixe) (n)''' : lieu caractéristique.<br>
'''εἶπα (verbe)''' : Forme ionienne de ''εἶπον''.<br>
'''εἴπην (verbe)''' : Forme dorienne de ''εἶπον''.<br>
'''ἔειπον (verbe)''' : Forme homérique de ''εἶπον''.<br>
'''εἶπον (verbe)''' : dire, parler.<br>
'''εἰρωνεία, -ας (nom commun) (f)''' : dissimulation.<br>
'''εἴρων, -ος (nom commun) (m/f)''' : dissimulateur.<br>
'''εἰσϐάλλω (verbe)''' : envahir.<br>
'''εἰσϐδάλλω (verbe)''' : .<br>
'''εἰσϐολέυς, -έως (nom commun) (m)''' : envahisseur.<br>
'''εἰσϐολή, -ῆς (nom commun) (f)''' : invasion.<br>
'''εἰσχωρῶ (verbe)''' : pénétrer.<br>
'''ἕκαστος, -άστη, -αστον (adjectif)''' : Chaque, chacun.<br>
'''ἑκάτερος, -έρα, -άτερον (adjectif)''' : L’un de deux, chacun des deux.<br>
'''ἑκατόν (adjectif numéral)''' : cent.<br>
'''ἑκατομμύριον, -ίου (nom commun) (n)''' : million.<br>
'''ἑκατονταρχία, -ας (nom commun) (f)''' : centurie.<br>
'''ἑκατόνταρχος, -άρχου (nom commun) (m)''' : centurion.<br>
'''ἐκϐιάζω (verbe)''' : faire chanter.<br>
'''ἐκϐιασμός, -οῦ (nom commun) (m)''' : chantage.<br>
'''ἐκδίδω (verbe)''' : éditer.<br>
'''ἐκδίκησις, -ήσεως (nom commun) (f)''' : vengeance.<br>
'''ἐκδικητής, -οῦ (nom commun) (m)''' : vengeur.<br>
'''ἐκδικητικός, -ή, -ον (adjectif)''' : vengeur.<br>
'''ἐκδικῶ (verbe)''' : venger.<br>
'''ἔκδοσις, -όσεως (nom commun) (f)''' : édition.<br>
'''ἐκδοχή, -ῆς (nom commun) (f)''' : version.<br>
'''ἐκδύω (verbe)''' : Faire disparaître, ôter.<br>
'''ἐκ (adverbe ; préposition ; préfixe)''' (Devient ''ἐξ'' devant un mot commençant par une voyelle, et ''ἐγ'' devant un mot commençant par ''β'', ''δ'', ''λ'' ou ''μ''.) : Hors, dehors.<br>
'''ἐκεῖθεν (adverbe démonstratif)''' : de là-bas.<br>
'''ἐκεῖ (adverbe démonstratif)''' : là-bas (sans mouvement).<br>
'''ἐκεῖσε (adverbe démonstratif)''' : là-bas (avec mouvement).<br>
'''ἐκείνῃ (adverbe démonstratif)''' : par là-bas.<br>
'''ἔκζεμα, -έματος (nom commun) (n)''' : eczéma.<br>
'''ἑκηϐόλος, -όλου (nom commun) (m)''' : tireur d’élite.<br>
'''ἔκθεσις, -έσεως (nom commun) (f)''' : exposition.<br>
'''ἐκκαλέω (verbe)''' : sommer.<br>
'''ἐκκένωσις, -ώσεως (nom commun) (f)''' : évacuation.<br>
'''ἐκκενῶ (verbe)''' : évacuer.<br>
'''ἐκκεντρικός, -ή, -όν (adjectif)''' : excentrique.<br>
'''ἐκκίνησις, -ήσεως (nom commun) (f)''' : départ.<br>
'''ἐκκλεισία, -ας (nom commun) (f)''' : Forme thessalienne de ''ἐκκλησία''.<br>
'''ἐκκλησία, -ας (nom commun) (f)''' : assemblée.<br>
'''ἐκκλησίασμα, -άσματος (nom commun) (n)''' : congrégation.<br>
'''ἐκλέγω (verbe)''' : choisir, sélectionner.<br>
'''ἐκλεκτικός, -ή, -όν (adjectif)''' : sélectif.<br>
'''ἐκλογή, -ῆς (nom commun) (f)''' : choix, élection.<br>
'''ἐκμιαίνομαι (verbe)''' : éjaculer.<br>
'''ἐκπομπή, -ῆς (nom commun) (f)''' : émission.<br>
'''ἐκπέμπω (verbe)''' : émettre.<br>
'''ἔκστασις, -άσεως (nom commun) (f)''' : transport spirituel.<br>
'''ἐκτάμνω (verbe)''' : Forme homérique et ionienne de ''ἐκτέμνω''.<br>
'''ἐκτέμνω (verbe)''' : .<br>
'''ἐκθέτης, -ου (nom commun) (m)''' : exposant.<br>
'''ἐκθετικός, -ή, -όν (adjectif)''' : exponentiel.<br>
'''ἔκρηξις, -ήξεως (nom commun) (f)''' : explosion.<br>
'''ἐκτίθημι (verbe)''' : exposer.<br>
'''ἑκτικός, -ή, -όν (adjectif)''' : habituel.<br>
'''ἑκτικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἑκτικός''.<br>
'''ἑκτικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἑκτικός''.<br>
'''ἑκτικῶς (adverbe)''' : habituellement.<br>
'''ἐκτιμῶ (verbe)''' : estimer.<br>
'''-εκτομία, -ας (suffixe)''' : action de couper.<br>
'''ἐκτός (adverbe ; préposition)''' : au-dehors ; hors de.<br>
'''ἑκυρά, -ᾶς (nom commun) (f)''' : belle-mère.<br>
'''ἑκυρός, -οῦ (nom commun) (m)''' : beau-père.<br>
'''ἐκφεύγω (verbe)''' : échapper à.<br>
'''ἔκφρασις, -άσεως (nom commun) (f)''' : expression.<br>
'''ἐκφράζω (verbe)''' : exprimer.<br>
'''ἐκφοϐῶ (verbe)''' : intimider.<br>
'''ἐκχύμωμα, -ώµατος (nom commun) (n)''' : Forme alternative de ''ἐκχύμωσις''.<br>
'''ἐκχύμωσις, -ώσεως (nom commun) (f)''' : bleu.<br> (tache de sang extravasé).<br>
'''ἐκχῶ (verbe)''' : s’écouler.<br>
'''ἐλάσσων, -ων, -ον (adjectif)''' : Comparatif de ''ἐλαχύς''.<br>
'''ἐλάττωμα, -ώµατος (nom commun) (n)''' : vice.<br>
'''ἔλαφος, -άφου (nom commun) (m/f)''' : Cerf ; biche.<br>
'''ἐλαφρός, -ή, -όν (adjectif)''' : léger, leste ; agile.<br>
'''ἐλαφρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἐλαφρός''.<br>
'''ἐλαφρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἐλαφρός''.<br>
'''ἐλαφρῶς (adverbe)''' : légèrement, lestement ; agilement.<br>
'''ἐλαχέως (adverbe)''' : petitement, courtement ; moyennement.<br>
'''ἐλάχιστος, -ίστη, -άχιστον (adjectif)''' : Superlatif de ''ἐλαχύς''.<br>
'''ἐλαχύς, -άχεια, -ύ (adjectif)''' : petit, court ; moyen.<br>
'''ἔλεγχος, -έγχους (nom commun) (n)''' : examen ; réfutation.<br>
'''ἐλέγχω (verbe)''' : examiner ; réfuter.<br>
'''ἐλεεινός, -ή, -όν (adjectif)''' : compatissant.<br>
'''ἐλέφας, -αντος (nom commun) (m)''' : Éléphant ; dent d’éléphant ; défense d’éléphant, ivoire.
Objet garni d’ivoire ou ressemblant à de l’ivoire.<br>
'''ἐλεημοσύνη, -ης (nom commun) (f)''' : aumône.<br>
'''ἐλεημονέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἐλεήμων''.<br>
'''ἐλεημονέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἐλεήμων''.<br>
'''ἐλεήμονως (adverbe)''' : charitablement.<br>
'''ἐλεήμων, -ων, -ον (adjectif)''' : charitable.<br>
'''ἔλεος, -έου (nom commun) (f)''' : Pitié ; compassion.<br>
'''ἐλευθερία, -ας (nom commun) (f)''' : liberté.<br>
'''ἐλευθερίη, -ας (nom commun) (f)''' : Forme ionienne de ''ἐλευθερία''.<br>
'''ἐλεύθερος, -έρα, ύθερον (adjectif)''' : libre. Qui convient à un homme libre, digne d’un homme libre.<br>
'''ἐλευθέρως (adverbe)''' : librement.<br>
'''ἐλευθερώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐλεύθερος''.<br>
'''ἐλευθερώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐλεύθερος''.<br>
'''ἐλελεῦ (verbe)''' : pousser un cri de guerre.<br>
'''ἐλελελεῦ (nom commun)''' : cri de guerre.<br>
'''ἐλεῶ (verbe)''' : avoir pitié.<br>
'''ἕλιξ, -κος (nom commun) (f)''' : Spirale, vrille. (Par analogie) Oreille externe.<br>
'''ἑλίσσω (verbe)''' : tourner autour.<br>
'''ἕλκηθρον, -ήτρου (nom commun) (n)''' : traîneau.<br>
'''ἕλκω (verbe)''' : hisser.<br>
'''ἐλλείπω (verbe)''' : Laisser derrière soi. Laisser de côté, négliger ; omettre. Manquer, faire défaut. (Intransitif) Rester en arrière.<br>
'''ἔλλειψις, -ίψεως (nom commun) (f)''' : Manque, défaut ; insuffisance.<br>
'''ἑλληνίζω (verbe)''' : parler grec.<br>
'''ἑλληνικός, -ή, -όν (adjectif)''' : grec.<br>
'''ἑλληνικῶς (adverbe)''' : .<br>
'''ἑλληνικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἑλληνικός''.<br>
'''ἑλληνικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἑλληνικός''.<br>
'''ἑλληνιστής, -οῦ (nom commun) (m)''' : homme parlant grec.<br>
'''ἑλληνιστί (adverbe)''' : en grec.<br>
'''ἑλληνίστρια, -ας (nom commun) (f)''' : femme parlant grec.<br>
'''ἐλλός, -οῦ (nom commun) (m)''' : faon.<br>
'''ἐλπίς, -δος (nom commun) (f)''' : espoir.<br>
'''ἔλυτρον, -ύτρου (nom commun) (n)''' : Enveloppe, étui, fourreau. (Par extension) Tout ce qui sert d’enveloppe.<br>
'''ἐλύω (verbe)''' : Entourer, rouler autour.<br>
'''ἔμϐρυον, -ύου (nom commun) (n)''' : embryon.<br>
'''ἔμεσις, -έσεως (nom commun) (f)''' : .<br>
'''ἐμετικός, -ή, -όν (adjectif)''' : vomitif.<br>
'''ἔμετος, -έτου (nom commun) (f)''' : vomissement.<br>
'''ἐµμί (verbe)''' : Forme éolienne de ''εἰμί''.<br>
'''ἐμῶ (verbe)''' : vomir.<br>
'''ἐμμονή, -ῆς (nom commun) (f)''' : obsession.<br>
'''ἐμός, -ή, -όν (adjectif possessif)''' : mon.<br>
'''ἐμπειρία, -ας (nom commun) (f)''' : expérience.<br>
'''ἐμπειρικός, -ή, -όν (adjectif)''' : expérienciel.<br>
'''ἐμπειρικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἐμπειρικός''.<br>
'''ἐμπειρικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἐμπειρικός''.<br>
'''ἐμπειρικότατα, -, - (adverbe)''' : Superlatif de ''ἐμπειρικῶς''.<br>
'''ἐμπειρικότερον, -, - (adverbe)''' : Comparatif de ''ἐμπειρικῶς''.<br>
'''ἐμπειρικῶς (adverbe)''' : expérienciellement.<br>
'''ἐμπλάσσω (verbe)''' : emplâtrer.<br>
'''ἔμπλαστρον, -άστρου (nom commun) (n)''' : emplâtre.<br>
'''ἔμπνευσις, -ύσεως (nom commun) (f)''' : .<br>
'''ἐμπορεῖον, -ίου (nom commun) (n)''' : boutique.<br>
'''ἐμπόρευμα, -ύματος (nom commun) (n)''' : marchandise.<br>
'''ἐμπορεύομαι (verbe)''' : commercer.<br>
'''ἐμπόριον, -ίου (nom commun) (n)''' : commerce.<br>
'''ἔμπορος, -όρου (nom commun) (m)''' : marchand.<br>
'''ἐμπρός (verbe)''' : devant.<br>
'''ἐμφαίνω (verbe)''' : Montrer ; présenter.<br>
'''ἐμφανής, -ής, -ές (adjectif)''' : apparent.<br>
'''ἐμφανίζω (verbe)''' : apparaître.<br>
'''ἐμφάνισις, -ίσεως (nom commun) (f)''' : apparence.<br>
'''ἔμφασις, -άσεως (nom commun) (f)''' : apparence.<br>
'''ἔμφραγμα, -άγματος (nom commun) (n)''' : infarctus.<br>
'''ἐμφρουρῶ (verbe)''' : .<br>
'''ἐμφύσημα, -ήματος (nom commun) (n)''' : inflation.<br>
'''ἐμφυσηματώδης, -ης, -ες (adjectif)''' : inflationnaire.<br>
'''ἐναλλακτικός, -ή, -όν (adjectif)''' : alternatif.<br>
'''ἐναλλακτικῶς (adverbe)''' : alternativement.<br>
'''ἐναλλακτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐναλλακτικός''.<br>
'''ἐναλλακτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐναλλακτικός''.<br>
'''ἐνάρετος, -ος, -ον (adjectif)''' : vertueux.<br>
'''ἔνδεια, -ας (nom commun) (f)''' : indigence.<br>
'''ἐνδεής, -ής, -ής (adjectif)''' : indigent.<br>
'''ἐνδόμυχος, -ος, -ον (adjectif)''' : intime.<br>
'''ἔνδον (adverbe ; préposition)''' : en dedans, intérieurement, à l'intérieur ; au-dedans de, à l'intérieur de.<br>
'''ἔνδος (adverbe ; préposition)''' : Forme dorienne de ''ἔνδον''.<br>
'''ἐνδοτάτω (adverbe)''' : Superlatif de ''ἔνδον''.<br>
'''ἐνδοτέρω (adverbe)''' : Comparatif de ''ἔνδον''.<br>
'''ἔνδυμα, -ύματος (nom commun) (n)''' : vêtement.<br>
'''ἐνέργεια, -ίας (nom commun) (f)''' : force en action.<br>
'''ἐνεργής, -ής, -ής (adjectif)''' : productif.<br>
'''ἐνεργός, -ός, -όν (adjectif)''' : actif.<br>
'''ἐνεργούμενος, -ένου (nom commun) (m)''' : énergumène.<br>
'''ἐνεργῶ (verbe)''' : Agir, produire, accomplir, exécuter. Agir sur, influencer (particulièrement en mauvaise part en parlant du mauvais esprit).
(Voie moyenne) Opérer, agir.<br>
'''ἐνενήκοντα (adjectif numéral)''' : quatre-vingt-dix.<br>
'''ἐνέχομαι (verbe)''' : .<br>
'''ἔνθα (adverbe)''' : ici.<br>
'''ἐνθάδε (adverbe)''' : ici-même.<br>
'''ἐνθένδε (adverbe)''' : d’ici.<br>
'''ἐννέα (adjectif numéral)''' : neuf.<br>
'''ἔνεμα, -έματος (nom commun) (n)''' : lavement. (Remède liquide.)<br>
'''ἔνεσις, -έσεως (nom commun) (f)''' : injection.<br>
'''ἐνεστώς, -ῶτος (nom commun) (n)''' : présent.<br>
'''ἐννῆ (adjectif numéral)''' : Forme de ''ἐννέα''.<br>
'''ἐνέχομαι (verbe)''' : .<br>
'''ἐνίημι (verbe)''' : injecter.<br>
'''ἐν (adverbe ; préposition)''' : Dans, en, parmi.<br>
'''ἐν- (préfixe)''' (Devient ''ἐγ-'' devant ''γ'', ''κ'', ''ξ'', ''χ'' ; ''ἐλ-'' devant ''λ'' ; ''ἐμ-'' devant ''β'', ''µ'', ''π'', ''φ'', ''ψ'' ; ''ἐρ-'' devant ''ρ'' dans quelques mots comme ''ἔρρινον'' ; ''ἐσ-'' devant ''σ''.) : in-.<br>
'''ἐνηλικίωσις, -ώσεως (nom commun) (f)''' : majorité.<br>
'''ἐνήλικος -η -ον (adjectif)''' : adulte.<br>
'''ἐνῆλιξ, -ήλικος (nom commun) (m)''' : adulte.<br>
'''ἔνθεσις, -έσεως (nom commun) (f)''' : enthèse.<br>
'''ἐνθουσιασμός, -οῦ (nom commun) (m)''' : enthousiasme.<br>
'''ἐνθουσιαστικός, -ή, -όν (adjectif)''' : enthousiasmant.<br>
'''ἐνθουσιαστικῶς (adverbe)''' : -ment.<br>
'''ἐνθουσιαστικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐνθουσιαστικός''.<br>
'''ἐνθουσιαστικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐνθουσιαστικός''.<br>
'''ἐνθουσιαστικώτατα, -, - (adverbe)''' : Superlatif de ''ἐνθουσιαστικῶς''.<br>
'''ἐνθουσιαστικώτερον, -, - (adverbe)''' : Comparatif de ''ἐνθουσιαστικῶς''.<br>
'''ἐνθουσιώδης, -ης, -ες (adjectif)''' : enthousiaste.<br>
''', -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐνθουσιώδης''.<br>
''', -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐνθουσιώδης''.<br>
''', -, - (adverbe)''' : Superlatif de ''''.<br>
''', -, - (adverbe)''' : Comparatif de ''''.<br>
'''ἐνίοτε (adverbe)''' : quelquefois.<br>
'''ἔννοια, -ίας (nom commun) (f)''' : concept.<br>
'''ἐνοριακός, -ή, -όν (adjectif)''' : paroissial.<br>
'''ἐνορία, -ας (nom commun) (f)''' : paroisse.<br>
'''ἐνοχή, -ῆς (nom commun) (f)''' : culpabilité.<br>
'''ἕννυμι (verbe)''' : enfiler. (mettre un vêtement)<br>
'''ἑνότης, -τος (nom commun) (f)''' : unité.<br>
'''ἐνούρησις, -ήσεως (nom commun) (f)''' : incontinence urinaire.<br>
'''ἐνουρῶ (verbe)''' : ne pas pouvoir retenir son urine.<br>
'''ἐνοχή, -ῆς (nom commun) (f)''' : culpabilité.<br>
'''ἐνόχλησις, -ήσεως (nom commun) (f)''' : Dérangement ; embêtement.<br>
'''ἐνοχλητικός, -ή, -όν (adjectif)''' : Dérangeant ; embêtant.<br>
'''ἐνοχλῶ (verbe)''' : Déranger ; embêter.<br>
'''ἐνσκήπτω (verbe)''' : sévir.<br>
'''ἔνταξις, -άξεως (f)''' : insertion.<br>
'''ἐντάσσω (verbe)''' : insérer.<br>
'''ἐνταῦθα (adverbe)''' : là.<br>
'''ἐντίθημι (verbe)''' : introduire.<br>
'''ἐντρέπω (verbe)''' : .<br>
'''ἐντύπωσις, -ώσεως (nom commun) (f)''' : impression.<br>
'''ἐντυπῶ (verbe)''' : .<br>
'''ἕνωσις, -ώσεως (nom commun) (f)''' : union.<br>
'''ἐνώτιον, -ίου (nom commun) (n)''' : boucle d'oreille.<br>
'''ἔορ, -ος (nom commun) (f)''' : Fille ; cousine.<br>
'''ἐρείκη, -ης (nom commun) (f)''' : bruyère.<br>
'''ἑορτάζω (verbe)''' : fêter.<br>
'''ἑορτή, -ῆς (nom commun) (f)''' : fête.<br>
'''ἐξαδελφή, -ῆς (nom commun) (f)''' : cousine.<br>
'''ἐξάδελφος, -ου (nom commun) (m)''' : cousin.<br>
'''ἐξαγοράζω (verbe)''' : soudoyer.<br>
'''ἐξαίρεσις, -έσεως (nom commun) (m)''' : extraction.<br>
'''ἐξαίρω (verbe)''' : exalter.<br>
'''ἐξαιρῶ (verbe)''' : retirer.<br>
'''ἐξαίσιος, -ια, -ιον (adjectif)''' : .<br>
'''ἑξαν (adverbe)''' : Forme dorienne de ''ἑξῆς''.<br>
'''ἔξαρσις, -άρσεως (nom commun) (f)''' : exaltation.<br>
'''ἐξάρτημα, -ήματος (nom commun) (n)''' : accessoire.<br>
'''ἕξ (adjectif numéral) (m/f/n)''' : six.<br>
'''ϝέξ (adjectif numéral) (m/f/n)''' : Forme dorienne de ''ἕξ''.<br>
'''ἑξακόσιοι (adjectif numéral)''' : six-cents.<br>
'''ἐξάντλησις, -ήσεως (nom commun) (f)''' : épuisement.<br>
'''ἐξαντλῶ (verbe)''' : épuiser.<br>
'''ἐξαπάτησις, -ήσεως (nom commun) (f)''' : supercherie.<br>
'''ἐξαπατῶ (verbe)''' : .<br>
'''ἐξέδρα, -ας (nom commun) (f)''' : plate-forme.<br>
'''ἐξέγερσις, -έρσεως (nom commun) (f)''' : insurrection.<br>
'''ἑξείης (adverbe)''' : Forme poétique de ''ἑξῆς''.<br>
'''ἐξήγησις, -ήσεως (nom commun) (f)''' : Exposé. Explication.<br>
'''ἐξηγητής, -ου (nom commun) (m)''' : .<br>
'''ἐξἡγοῦμαι (verbe)''' : Conduire, exposer.<br>
'''ἑξήκοντα (adjectif numéral)''' : soixante.<br>
'''ἑξῆς (adverbe)''' : .<br>
'''ἐξιλέωσις, -ώσεως (nom commun) (f)''' : expiation.<br>
'''ἐξιλεῶ (verbe)''' : expier.<br>
'''ἐξέλιξις, -ίξεως (nom commun) (f)''' : évolution.<br>
'''ἐξελίσσω (verbe)''' : évoluer.<br>
'''ἐξευτελίζω (verbe)''' : humilier.<br>
'''ἐξέτασις, -άσεως (nom commun) (f)''' : examen, recherche.<br>
'''ἐξίστημι (verbe)''' : déplacer.<br>
'''ἕξις, -εως (nom commun) (f)''' : habitude.<br>
'''ἐξόγκωμα, -ώματος (nom commun) (n)''' : Proéminence ; protubérance.<br>
'''ἔξοδος, -ου (nom commun) (f)''' : Issue ; sortie, départ.<br>
'''ἐξομολόγησις, -ήσεως (nom commun) (f)''' : confession.<br>
'''ἐξομολογητήριον, -ίου (nom commun) (n)''' : confessionnal.<br>
'''ἐξομολογητής, -οῦ (nom commun) (m)''' : confesseur.<br>
'''ἐξομολογῶ (verbe)''' : confesser.<br>
'''ἐξορία, -ας (nom commun) (f)''' : exil.<br>
'''ἐξορίζω (verbe)''' : exorciser.<br>
'''ἐξορκισμός, -οῦ (nom commun) (m)''' : exorcisme.<br>
'''ἐξορκιστής, -οῦ (nom commun) (m)''' : exorciste.<br>
'''ἐξύπνημα, -ήματος (nom commun) (n)''' : réveil.<br>
'''ἔξυπνος, -ος, -ον (adjectif)''' : .<br>
'''ἐξυπνῶ (verbe)''' : réveiller.<br>
'''ἐξωθῶ (verbe)''' : .<br>
'''ἐξωκκλήσιον, -ίου (nom commun) (n)''' : chapelle.<br>
'''ἐξωµίς, -δος (nom commun) (f)''' : exomide.<br>
'''ἐξώστης, -ου (nom commun) (m)''' : balcon.<br>
'''ἐξωτερικός, -ή, -όν (adjectif)''' : extérieur, externe.<br>
'''ἐξωτερικῶς (adverbe)''' : extérieurement.<br>
'''ἐξωτερικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐξωτερικός''.<br>
'''ἐξωτερικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐξωτερικός''.<br>
'''ἐξωτερικώτατα, -, - (adverbe)''' : Superlatif de ''ἐξωτερικῶς''.<br>
'''ἐξωτερικώτερον, -, - (adverbe)''' : Comparatif de ''ἐξωτερικῶς''.<br>
'''ἐξωτικός, -ή, -όν (adjectif)''' : étranger.<br>
'''ἐξωτικῶς (adverbe)''' : exotiquement.<br>
'''ἐξωτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐξωτικός''.<br>
'''ἐξωτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐξωτικός''.<br>
'''ἐξωτικώτατα, -, - (adverbe)''' : Superlatif de ''ἐξωτικῶς''.<br>
'''ἐξωτικώτερον, -, - (adverbe)''' : Comparatif de ''ἐξωτικῶς''.<br>
'''ἔξω (adverbe)''' : hors de.<br>
'''ἐπαινῶ (verbe)''' : vanter.<br>
'''ἐπαναλαμϐάνω (verbe)''' : répéter ; reprendre.<br>
'''ἐπαλήθευσις, -ύσεως (nom commun) (f)''' : vérification.<br>
'''επαληθευτικός, -ή, -όν (adjectif)''' : vérificatif.<br>
'''ἐπαληθευτικῶς (adverbe)''' : vérificativement.<br>
'''ἐπαληθευτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''επαληθευτικός''.<br>
'''ἐπαληθευτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''επαληθευτικός''.<br>
'''ἐπαληθευτικώτατα, -, - (adverbe)''' : Superlatif de ''επαληθευτικῶς''.<br>
'''ἐπαληθευτικώτερον, -, - (adverbe)''' : Comparatif de ''επαληθευτικῶς''.<br>
'''ἐπαληθεύω (verbe)''' : vérifier.<br>
'''ἐπαναληπτικός, -ή, -όν (adjectif)''' : répétitif.<br>
'''ἐπαναληπτικῶς (adverbe)''' : répétitivement.<br>
'''ἐπαναληπτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐπαναληπτικός''.<br>
'''ἐπαναληπτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐπαναληπτικός''.<br>
'''ἐπαναληπτικώτατα, -, - (adverbe)''' : Superlatif de ''ἐπαναληπτικῶς''.<br>
'''ἐπαναληπτικώτερον, -, - (adverbe)''' : Comparatif de ''ἐπαναληπτικῶς''.<br>
'''ἐπανάληψις, -ήψεως (nom commun) (f)''' : répétition.<br>
'''ἐπανάστασις, -άσεως (nom commun) (f)''' : révolution.<br>
'''ἐπανεκκίνησις, -ήσεως (nom commun) (f)''' : redémarrage.<br>
'''ἐπάνω (adverbe)''' : .<br>
'''ἐπαρχία, -ας (nom commun) (f)''' : province.<br>
'''ἔπαρχος, -άρχου (nom commun) (m)''' : .<br>
'''ἐπάρχω (verbe)''' : .<br>
'''ἐπαφή, -ῆς (nom commun) (f)''' : contact.<br>
'''ἐπαφίημι (verbe)''' : contacter.<br>
'''ἐπεξῆς (adverbe)''' : Forme ionienne de ''ἐφεξῆς''.<br>
'''ἐπένδυσις, -ύσεως (nom commun) (f)''' : .<br>
'''ἐπενδύω (verbe)''' : revêtir par-dessus.<br>
'''ἐπένθεσις, -έσεως (nom commun) (f)''' (Grammaire) Insertion d’une lettre à l’intérieur d’un mot.<br>
'''ἐπεντίθημι (verbe)''' : Insérer une lettre à l’intérieur d’un mot.<br>
'''ἐπερώτησις, -ήσεως (nom commun) (f)''' : interpellation.<br>
'''ἑπτά (adjectif numéral)''' : sept.<br>
'''ἐπεισοδιακός, -ή, -όν (adjectif)''' : épisodique.<br>
'''ἐπεισόδιον, -ου (nom commun) (n)''' : épisode.<br>
'''ἐπιϐαίνω (verbe)''' : Monter. (Marine) Embarquer, monter dans un bateau. (Militaire) Attaquer, avancer sur l’ennemi. Monter à cheval, aller à cheval.<br>
'''ἐπιϐδάλλω (verbe)''' : .<br>
'''ἐπιϐήτωρ, -ορος (nom commun) (m)''' : étalon.<br>
'''ἐπιϐλαϐής, -ής, -ές (adjectif)''' : .<br>
'''ἐπίϐλημα, -ήματος (nom commun) (m)''' : châle.<br>
'''ἐπίγραθμα, -άθματος (nom commun) (n)''' : Forme dorienne de ''ἐπίγραμμα''.<br>
'''ἐπίγραμμα, -άμματος (nom commun) (n)''' : inscription.<br>
'''ἐπίδειξις, -ίξεως (nom commun) (f)''' : exhibition.<br>
'''ἐπιδεκτικός, -ή, -όν (adjectif)''' : .<br>
'''ἐπιδέχομαι (verbe)''' : .<br>
'''ἐπιείκεια, -ίας (nom commun) (f)''' : indulgence.<br>
'''ἐπιεικής, -ής, -ές (adjectif)''' : indulgent.<br>
'''ἐπί (adverbe)''' (Devient ''ἐπ<nowiki>'</nowiki>'' devant une voyelle avec esprit doux et ''ἐφ<nowiki>'</nowiki>'' devant une voyelle avec esprit rude.) : sur.<br>
'''ἐπιθαλάμιον, -ίου (nom commun) (n)''' : .<br>
'''ἐπιθιγγάνω (verbe)''' : .<br>
'''ἐπιτιθέναι (verbe)''' : .<br>
'''ἐπίθεσις, -έσεως (nom commun) (f)''' : attaque ; assaut.<br>
'''ἐπίθετον, -έτου (nom commun) (n)''' : adjectif.<br>
'''ἐπίθετος, -, - (adjectif)''' : adjectival.<br>
'''ἐπίθημα, -ήματος (nom commun) (n)''' : .<br>
'''ἐπιθυμέω (verbe)''' : désirer.<br>
'''ἐπιθυμία, -ας (nom commun) (f)''' : Désir, souhait. Passion.<br>
'''ἐπίθυμος, -, - (adjectif)''' : Désireux ; passionné.<br>
'''ἐπικαλοῦμαι (verbe)''' : invoquer.<br>
'''ἐπικαλύπτω (verbe)''' : recouvrir.<br>
'''ἐπικάλυψις, -ύψεως (nom commun) (f)''' : recouvrement.<br>
'''ἐπικός, -ή, -όν (adjectif)''' : relatif aux vers.<br>
'''ἐπικότατα, -, - (adverbe)''' : Superlatif de ''ἐπικῶς''.<br>
'''ἐπικότερον, -, - (adverbe)''' : Comparatif de ''ἐπικῶς''.<br>
'''ἐπικύρωσις, -ώσεως (nom commun) (f)''' : ratification.<br>
'''ἐπικυρῶ (verbe)''' : ratifier.<br>
'''ἐπικῶς (adverbe)''' : épiquement.<br>
'''ἐπικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐπικός''.<br>
'''ἐπικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐπικός''.<br>
'''ἐπιλαμϐάνω (verbe)''' : invoquer.<br>
'''ἐπίληπτος, -ος, -ον (adjectif)''' : .<br>
'''ἐπιληψία, -ας (nom commun) (f)''' : .<br>
'''ἐπιλογή, -ῆς (nom commun) (f)''' : sélection.<br>
'''ἐπίλογος, -όγου (nom commun) (m)''' : postface.<br>
'''ἐπίπληξις, -ήξεως (nom commun) (f)''' : réprimande.<br>
'''ἐπιπλήττω (verbe)''' : réprimander.<br>
'''ἐπιτίθημι (verbe)''' : attaquer.<br>
'''ἐπιείκεια, -ίας (nom commun) (f)''' : indulgence.<br>
'''ἐπιεικής, -ής -ές (adjectif)''' : indulgent.<br>
'''ἐπιμέλεια, -ίας (nom commun) (f)''' : sollicitude.<br>
'''ἐπιμελής, -ής -ές (adjectif)''' : soigneux.<br>
'''ἐπιμέλησις, -ήσεως (nom commun) (f)''' : soin.<br>
'''ἐπιμελότατα, -, - (adverbe)''' : Superlatif de ''ἐπιμελῶς''.<br>
'''ἐπιμελότερον, -, - (adverbe)''' : Comparatif de ''ἐπιμελῶς''.<br>
'''επιμελοῦμαι (verbe)''' : prendre soin de, veiller à ; se préoccuper de.<br>
'''ἐπιμελῶς (adverbe)''' : soigneusement.<br>
'''ἐπιούσιος, -α, -ον (adjectif)''' : supersubstantiel.<br>
'''ἔπιπλον, -ίπλου (nom commun) (n)''' : fourniture.<br>
'''ἐπίρρημα, -ήματος (nom commun) (n)''' : adverbe.<br>
'''ἐπιρρίπτω (verbe)''' : .<br>
'''ἐπιρρωνύω (verbe)''' : corroborer.<br>
'''ἐπίρρωσις, -ώσεως (nom commun) (f)''' : corroboration.<br>
'''ἐπίσημος, -α, -ον''' : Marqué, noté. Notable, remarquable.<br>
'''ἐπισκέπτης, -ου (nom commun) (m)''' : visiteur.<br>
'''ἐπισκέπτομαι (verbe)''' : visiter.<br>
'''ἐπίσκεψις, -έψεως (nom commun) (f)''' : visite (action de visiter).<br>
'''ἐπιστάζω (verbe)''' : saigner du nez.<br>
'''ἐπίσταμαι (verbe)''' : Savoir ; connaitre.<br>
'''ἐπίσταξις, -άξεως (nom commun) (f)''' : saignement nasal.<br>
'''ἐπιστέλλω (verbe)''' : envoyer.<br>
'''ἐπιστήμη, -ης (nom commun) (f)''' : Science ; savoir.<br>
'''ἐπιστημονέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἐπιστήμων''.<br>
'''ἐπιστημονέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἐπιστήμων''.<br>
'''ἐπιστήμονως (adverbe)''' : savamment.<br>
'''ἐπιστήμων, -ων, -ον (adjectif)''' : Savant ; instruit.<br>
'''ἐπιστολή, -ῆς (nom commun) (f)''' : Ordre ou avis émis par un message verbal ou écrit. Message écrit, lettre.<br>
'''ἐπιστολικός, -ή, -όν (adjectif)''' : épistolaire.<br>
'''ἐπιστολικῶς (adverbe)''' : épistolairement.<br>
'''ἐπιστολικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐπιστολικός''.<br>
'''ἐπιστολικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐπιστολικός''.<br>
'''ἐπιστολικώτατα, -, - (adverbe)''' : Superlatif de ''ἐπιστολικῶς''.<br>
'''ἐπιστολικώτερον, -, - (adverbe)''' : Comparatif de ''ἐπιστολικῶς''.<br>
'''ἐπιστροφή, -ῆς (nom commun) (f)''' : retour.<br>
'''ἐπιτακτικός, -ή, -όν (adjectif)''' : impératif.<br>
'''ἐπιτακτικῶς (adverbe)''' : impérativement.<br>
'''ἐπιτακτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐπιτακτικός''.<br>
'''ἐπιτακτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐπιτακτικός''.<br>
'''ἐπιτακτικώτατα, -, - (adverbe)''' : Superlatif de ''ἐπιτακτικῶς''.<br>
'''ἐπιτακτικώτερον, -, - (adverbe)''' : Comparatif de ''ἐπιτακτικῶς''.<br>
'''ἐπιτέμνω (verbe)''' : .<br>
'''ἐπιτήδειος, -α, -ον (adjectif)''' : commode ; pratique.<br>
'''ἐπιτηδειότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἐπιτήδειος''.<br>
'''ἐπιτηδειότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἐπιτήδειος''.<br>
'''ἐπιτηδειότατα, -, - (adverbe)''' : Superlatif de ''ἐπιτηδείως''.<br>
'''ἐπιτηδειότερον, -, - (adverbe)''' : Comparatif de ''ἐπιτηδείως''.<br>
'''ἐπιτηδείως (adverbe)''' : commodément.<br>
'''ἐπιτομή, -ῆς (nom commun) (f)''' : compendium.<br>
'''ἐπιτόνιον, -ίου (nom commun) (n)''' : .<br>
'''ἐπιτρέπω (verbe)''' : autoriser, permettre.<br>
'''ἐπιφέρω (verbe)''' : .<br>
'''ἐπιφώνημα, -ήματος (nom commun) (n)''' : interjection.<br>
'''ἐπιφώνησις, -ήσεως (nom commun) (f)''' : .<br>
'''ἐπιφωνῶ (verbe)''' : .<br>
'''ἐπιχαιρεκακία, -ας (nom commun) (f)''' : .<br>
'''ἐπιχαιρέκακος, -ος, -ον (adjectif)''' : .<br>
'''ἐπιχαίρω (verbe)''' : réjouir.<br>
'''ἐπιχείρημα, -ήματος (nom commun) (n)''' : argument.<br>
'''ἐπιχειρῶ (verbe)''' : cicatriser.<br>
'''ἕπομαι (verbe)''' : argumenter.<br>
'''ἔπος, -ους (nom commun) (n)''' : Parole ; vers.<br>
'''ἐποποιία, -ας (nom commun) (f)''' : épopée.<br>
'''ἐπουλῶ (verbe)''' : cicatriser.<br>
'''ἐπύλλιον, -ίου (nom commun) (n)''' : .<br>
'''ἐπῳδή, -ῆς (nom commun) (f)''' : incantation.<br>
'''ἐπώδυνος, -ος, -ον (adjectif)''' : douloureux. Causé par la douleur.<br>
'''ἑπωµίς, -δος (nom commun) (f)''' : épaulette.<br>
'''ἐπωνύμιον, -ίου (nom commun) (n)''' : nom de famille.<br>
'''ἐπωφελής, -ής, -ές (adjectif)''' : .<br>
'''ἔραμαι (verbe)''' : aimer d'amour, désirer.<br>
'''ἔρανος, -άνου (nom commun) (m)''' : quête.<br>
'''ἐραστής, -οῦ (nom commun) (m)''' : amant.<br>
'''ἑραστός, -ή, -όν (adjectif)''' : aimable, amoureux.<br>
'''ἐράστρια, -ας (nom commun) (f)''' : amante.<br>
'''ἐραστριάω (verbe)''' : être amoureux.<br>
'''ἐρέϐινθος, -ίνθου (nom commun) (m)''' : pois chiche.<br>
'''ἔρεϐος, -έϐους (nom commun) (n)''' : obscurité.<br>
'''ἐρρωσθαι (verbe)''' : se bien porter.<br>
'''ἐργάζομαι (verbe)''' : travailler.<br>
'''ἐργαλεῖον, -ίου (nom commun) (n)''' : outil.<br>
'''ἐργαστήριακός, -ή -όν (adjectif)''' : laborantin.<br>
'''ἐργαστήριον, -ίου (nom commun) (n)''' : atelier, laboratoire.<br>
'''ἐργαστήρ, -ῆρος (nom commun) (m)''' : travailleur.<br>
'''ἐργάτης, -ου (nom commun) (m)''' : ouvrier, travailleur.<br>
'''ἔργον, -ου (nom commun) (n)''' : travail.<br>
'''ἐργώδης, -ης, -ες (adjectif)''' : laborieux.<br>
'''ϝέργον, -ου (nom commun) (n)''' : Forme dorienne de ''ἔργον''.<br>
'''ϝάργον, -ου (nom commun) (n)''' : Forme éléenne de ''ἔργον''.<br>
'''ἐρέσσω (verbe)''' : ramer.<br>
'''ἐρετμόν, -οῦ (nom commun) (n)''' : rame.<br>
'''ἐρεύγομαι (verbe)''' : roter.<br>
'''ἔρευνα, -ας (nom commun) (f)''' : enquête, recherche.<br>
'''ἐρευνητής, -οῦ (nom commun) (m)''' : enquêteur, chercheur.<br>
'''ἐρευνῶ (verbe)''' : enquêter, rechercher.<br>
'''ἐρέφω (verbe)''' : toiturer, couronner.<br>
'''ἐρημίτης, -ου (nom commun) (m)''' : ermite.<br>
'''ἔρημος, -ήμου (nom commun) (f)''' : désert.<br>
'''ἐριθακός, -οῦ (nom commun) (m)''' : rouge-gorge.<br>
'''ἐρίθακος, -άκου (nom commun) (m)''' : rouge-gorge.<br>
'''ἑρμηνεύς, -έως (nom commun) (m)''' : Interprète, traducteur ; messager des dieux.<br>
'''ἑρμηνευτικός, -ή, -όν (adjectif)''' : .<br>
'''ἑρμηνεύω (verbe)''' : Exprimer sa pensée par la parole. (Par suite) Faire connaître, indiquer, exposer (quelque chose). Interpréter, traduire.<br>
'''ἔρομαι (verbe)''' : Demander, enquêter ; s’enquérir de.<br>
'''ἑρπετόν, -οῦ (nom commun) (n)''' : Reptile ; serpent.<br>
'''ἕρπυλλος, -ύλλου (nom commun) (m)''' : serpolet.<br>
'''ἑρπυσμός, -οῦ (nom commun) (m)''' : fluage.<br>
'''ἐρῥωσθαι (verbe)''' : se bien porter.<br>
'''ἔρσην, -ενος (nom commun) (m)''' : Forme crétoise et éolienne de ''ἄρσην''.<br>
'''ἐρεύθω (verbe)''' : rougir.<br>
'''ἔρχομαι (verbe)''' : Venir, aller. S'en aller.<br>
'''ἐρύθημα, -ήματος (nom commun) (n)''' : érythème.<br>
'''ἐρυθρός, -ά, -όν (adjectif)''' : rouge.<br>
'''ἐρυσίπελας, -έλατος (nom commun) (n)''' : inflammation cutanée.<br>
'''ἐρωή, -ῆς (nom commun) (f)''' : précipitation.<br>
'''ἐρωδιός, -οῦ (nom commun) (m)''' : héron.<br>
'''ἐρωμένη, -ης (nom commun) (f)''' : femme aimé.<br>
'''ἐρώμενος, -ένου (nom commun) (m)''' : homme aimé.<br>
'''ἔρως, -τος (nom commun) (n)''' : amour « naturel » ; désir sexuel, plaisir corporel.<br>
'''ἐρώτημα, -ήματος (nom commun) (n)''' : question.<br>
'''ἐρωτηματικός, -ή, -όν (adjectif)''' : interrogatif.<br>
'''ἐρωτηματικότατα, -, - (adverbe)''' : Superlatif de ''ἐρωτηματικῶς''.<br>
'''ἐρωτηματικότερον, -, - (adverbe)''' : Comparatif de ''ἐρωτηματικῶς''.<br>
'''ἐρωτηματικῶς (adverbe)''' : érotiquement.<br>
'''ἐρωτηματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐρωτηματικός''.<br>
'''ἐρωτηματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐρωτηματικός''.<br>
'''ἐρώτησις, -ήσεως (nom commun) (f)''' : interrogation.<br>
'''ἐρωτικός, -ή, -όν (adjectif)''' : relatif à l'amour « naturel ».<br>
'''ἐρωτικότατα, -, - (adverbe)''' : Superlatif de ''ἐρωτικῶς''.<br>
'''ἐρωτικότερον, -, - (adverbe)''' : Comparatif de ''ἐρωτικῶς''.<br>
'''ἐρωτικῶς (adverbe)''' : érotiquement.<br>
'''ἐρωτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐρωτικός''.<br>
'''ἐρωτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐρωτικός''.<br>
'''ἐρωτῶ (verbe)''' : interroger.<br>
'''ἐρῶ (verbe)''' : aimer d'amour, désirer. Verser hors de ; vomir.<br>
'''ἐσσαῖος, -ίου (nom commun) (m)''' : essénien.<br>
'''ϝεσή, -ῆς (nom commun) (f)''' : toilettes.<br>
'''ἐσθής, -ήτος (nom commun) (f)''' : vêtement.<br>
'''ἐσθίω (verbe)''' : manger.<br>
'''ἐσθλός, -ή, -όν (adjectif)''' : Excellent ; (poétique) bon.<br>
'''ἐσθλῶς (adverbe)''' : excellemment.<br>
'''ἐσθλώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐσθλός''.<br>
'''ἐσθλώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἐσθλός''.<br>
'''ἐσλός, -ή, -όν (adjectif)''' : Forme dorienne de ''ἐσθλός''.<br>
'''ἑσπέρα, -ας (nom commun) (f)''' : Soir ; ouest.<br>
'''ϝεσπέρα, -ας (nom commun) (f)''' : Forme ancienne de ''ἑσπέρα''.<br>
'''ἕσπερος, -ος, -ον (adjectif)''' : Relatif au soir ; occidental.<br>
'''ἑστίασις, -άσεως (nom commun) (f)''' : restauration.<br>
'''ἑστιατόριον, -ίου (nom commun) (n)''' : restaurant.<br>
'''ἑστιάτωρ, -ορος (nom commun) (m)''' : restaurateur.<br>
'''ἑστιάω (verbe)''' recevoir chez soi.<br>
'''ἐσχάρα, -ας (nom commun) (f)''' : Foyer ; autel domestique et sanctuaire pour les suppliants. Autel pour les sacrifices. Brasier. Réchaud.<br>
'''ἐσχάριον, -ίου (nom commun) (n)''' : cale (dispositif maritime).<br>
'''ἔσχατος, -άτη, -ον (adjectif)''' : dernier.<br>
'''ἐσωτερικός, -ή, -όν (adjectif)''' : intérieur, interne.<br>
'''ἔσω (adverbe)''' : .<br>
'''ἐσωτάτω (adverbe)''' : Superlatif de ''ἔσω''.<br>
'''ἐσωτέρω (adverbe)''' : Comparatif de ''ἔσω''.<br>
'''ἑταῖρα, -ίρας (nom commun) (f)''' : compagne.<br>
'''ἑταιρείη, -ης (nom commun) (f)''' : Forme ionienne de ''ἑταιρεία''.<br>
'''ἑταιρεῖος, -ία, -ῖον (adjectif)''' : compagnon.<br>
'''ἑταῖρη, -ίρης (nom commun) (f)''' : Forme ionienne de ''ἑταῖρα''.<br>
'''ἑταίρησις, -ήσεως (nom commun) (f)''' : compagnie.<br>
'''ἑταῖρος, -ίρου (nom commun) (m)''' : compagnon.<br>
'''ἐτεός, -ά, -όν (adjectif)''' : .<br>
'''ἐτεῶς (adverbe)''' : .<br>
'''ἐτεώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἐτεός''.<br>
'''ἐτεώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de''ἐτεός''.<br>
'''ἔτης, -ου (nom commun) (m)''' : cousin ; voisin.<br>
'''ἔτος, -ους (nom commun) (n)''' : année.<br>
'''ϝέτος, -ους (nom commun) (n)''' : Forme ancienne de ''ἔτος''.<br>
'''ἔτυμος, -η, -ον (adjectif)''' : Vrai ; réel, véritable.<br>
'''εὐάζω (verbe)''' : .<br>
'''εὐαί (interjection)''' : .<br>
'''εὐαγγελίζω (verbe)''' : annoncer une bonne nouvelle.<br>
'''εὐαγγελισμός, -οῦ (nom commun) (m)''' : annonciation.<br>
'''εὖγμα, -ὔγματος (nom commun) (n)''' : vœu.<br>
'''εὐδαιμονία, -ας (nom commun) (f)''' : bonheur.<br>
'''εὐδαίμων, -ων, -ον (adjectif)''' : heureux.<br>
'''εὐδαιμόνως (adverbe)''' : heureusement.<br>
'''εὐδαιμονέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''εὐδαίμων''.<br>
'''εὐδαιμονέστερος, -έρα, -έρερον (adjectif)''' : Comparatif de ''εὐδαίμων''.<br>
'''εὕδω (verbe)''' : dormir.<br>
'''εὐγενής, -ής, -ές (adjectif)''' : noble.<br>
'''εὐεργεσία, -ας (nom commun) (f)''' : bénéfice.<br>
'''εὐεργέτης, -ου (nom commun) (n)''' : bienfaiteur.<br>
'''εὐεργέτις, -δος (nom commun) (f)''' : bienfaitrice.<br>
'''εὐεργετῶ (bénéfice)''' : bénéficier.<br>
'''εὐ- (préfixe)''' : bon, bien.<br>
'''εὐθέως (adverbe)''' : Directement ; immédiatement.<br>
'''εὔθυνα, -ύνης (nom commun) (f)''' : responsabilité.<br>
'''εὐθυνότης, -ητος (nom commun) (f)''' : responsabilité.<br>
'''εὐθύνω (verbe)''' : être responsable.<br>
'''εὐθύς, -εῖα, -ύ (adjectif)''' : Direct ; immédiat.<br>
'''εὐθύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''εὐθύς''.<br>
'''εὐθύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''εὐθύς''.<br>
'''εὐλάϐεια, -ας (nom commun) (f)''' : piété.<br>
'''εὐλαϐής, -ής, -ές (adjectif)''' : pieux.<br>
'''εὐλαϐικός, -ή, -όν (adjectif)''' : .<br>
'''εὐλαϐικῶς (adverbe)''' : avec piété.<br>
'''εὐλαϐῶς (adverbe)''' : pieusement.<br>
'''εὐλαλος, -ος, -ον (adjectif)''' : qui parle bien.<br>
'''εὐλογιά, -ᾶς (nom commun) (f)''' : variole.<br>
'''εὐλογία, -ας (nom commun) (f)''' : louange.<br>
'''εὐμετάϐλητος, -η, -ον (adjectif)''' : .<br>
'''εὐμορφία, -ας (nom commun) (f)''' : beauté.<br>
'''εὔμορφος, -ος, -ον (adjectif)''' : beau.<br>
'''εὔνοια, -ίας (nom commun) (f)''' : faveur.<br>
'''εὐνοϊκός, -ή, -όν (adjectif)''' : favorable.<br>
'''εὐνοϊκῶς (adverbe)''' : favorablement.<br>
'''εὐνοϊκώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''εὐνοϊκός''.<br>
'''εὐνοϊκώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''εὐνοϊκός''.<br>
'''εὐνοϊκώτατα, -, - (adverbe)''' : Superlatif de ''εὐνοϊκῶς''.<br>
'''εὐνοϊκώτερον, -, - (adverbe)''' : Comparatif de ''εὐνοϊκῶς''.<br>
'''εὔνους, -ους, -ουν (adjectif)''' : .<br>
'''εὐπατρίδης, -ου (nom commun) (m)''' : gentilhomme.<br>
'''εὐοπλέω (verbe)''' : être bien armé.<br>
'''εὐρετήριον, -ήριου (nom commun) (n)''' : index (liste).<br>
'''εὐρέως (adverbe)''' : largement.<br>
'''εὐρύς, -εῖα, -ύ (adjectif)''' : Large. (Par extension) Vaste, spacieux.<br>
'''εὐρύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''εὐρύς''.<br>
'''εὐρύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''εὐρύς''.<br>
'''εὔρωστος, -ος, -ον (adjectif)''' : .<br>
'''εὐρωστότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''εὔρωστος''.<br>
'''εὐρωστότερος, -έρα, -ότερον (adjectif)'''' : Comparatif de ''εὔρωστος''.<br>
'''εὐρωστότατα, -, - (adverbe)''' : Superlatif de ''εὐρώστως''.<br>
'''εὐρωστότερον, -, - (adverbe)''' : Comparatif de ''εὐρώστως''.<br>
'''εὐρώστως (adverbe)''' : .<br>
'''εὐσέϐεια, -ίας (nom commun) (f)''' : piété.<br>
'''εὐσεϐέστατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''εὐσεϐής''.<br>
'''εὐσεϐέστερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''εὐσεϐής''.<br>
'''εὐσεϐής, -ής, -ές (adjectif)''' : pieux.<br>
'''εὐσεϐῶς (adverbe)''' : pieusement.<br>
'''εὐσυνείδητος, -ος, -ον (adjectif)''' : consciencieux.<br>
'''ἐύς, -εῖα, -ύ (adjectif)''' : bon, brave.<br>
'''εὐτελέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''εὐτελής''.<br>
'''εὐτελέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''εὐτελής''.<br>
'''εὐτελής, -ής, -ές (adjectif)''' : Bon marché. De vil prix, de peu de valeur. Avare.<br>
'''εὐτελίζω (verbe)''' : mépriser.<br>
'''εὐτελῶς (adverbe)''' : .<br>
'''εὐτυχία, -ας (nom commun) (f)''' : bonheur.<br>
'''εὖ (adverbe)''' : Bien (idée d’origine) Noblement. Bien, régulièrement ; justement. Bien, avec bienveillance. Heureusement.<br>
'''εὐτυχέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''εὐτυχής''.<br>
'''εὐτυχέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''εὐτυχής''.<br>
'''εὐτυχής, -ής, -ές (adjectif)''' : heureux.<br>
'''εὐτυχῶς (adverbe)''' : heureusement.<br>
'''εὔφλεκτος, -ος, -ον (adjectif)''' : inflammable.<br>
'''εὐφραδέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''εὐφραδής''.<br>
'''εὐφραδέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''εὐφραδής''.<br>
'''εὐφραδής, -ής, -ές (adjectif)''' : .<br>
'''εὔφρων, -ων, -ον (adjectif)''' : .<br>
'''εὐφυέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''εὐφυής''.<br>
'''εὐφυέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''εὐφυής''.<br>
'''εὐφυής, -ής, -ές (adjectif)''' : .<br>
'''εὐφυῶς (adverbe)''' : .<br>
'''εὐφωνία, -ας (nom commun) (f)''' : bonne sonorité.<br>
'''εὐχαριστία, -ας (nom commun) (f)''' : .<br>
'''εὐχάριστος, -ος, -ον (adjectif)''' : .<br>
'''εὐχαριστῶ (verbe)''' : remercier.<br>
'''εὐχή, -ῆς (nom commun) (f)''' : vœu.<br>
'''εὔχομαι (verbe)''' : formuler un vœu.<br>
'''εὖχος, -ὔχους (nom commun) (n)''' : Prière, vœu.<br>
'''εὐχωλή, -ῆς (nom commun) (f)''' : Prière, vœu.<br>
'''-εύς, -έως (suffixe) (m)''' : .<br>
'''-εύω (suffixe)''' : .<br>
'''ἐφαμέριος, -ίου (nom commun) (m)''' : Forme dorienne de ''ἐφημέριος''.<br>
'''ἐφαμερεύω (verbe)''' : Forme dorienne de ''ἐφημερεύω''.<br>
'''ἐφαρμόζω (verbe)''' : appliquer.<br>
'''ἐφεξείης (adverbe)''' : Forme poétique de ''ἐφεξῆς''.<br>
'''ἐφεξῆς (adverbe)''' : dorénavant, désormais.<br>
'''ἐφήϐαιον, -ίου (nom commun) (m)''' : pubis.<br>
'''ἐφηϐεία, -ας (nom commun) (f)''' : Service militaire rassemblant les jeunes Athéniens âgés de 18 à 20 ans.<br>
'''ἐφηϐικός, -ή, -όν (adjectif)''' : adolescent.<br>
'''ἔφηϐος, -ήϐου (nom commun) (m)''' : Jeune garçon ayant quitté l’autorité des femmes.<br>
'''ἐφημέριος, -ίου (nom commun) (m)''' : curé.<br>
'''ἐφημερεύω (verbe)''' : .<br>
'''ἐφιάλτης, -ου (nom commun) (m)''' : cauchemar.<br>
'''ἔχθος, -ους (nom commun) (n)''' : Haine ; hostilité.<br>
'''ἐχθρά, -ᾶς (nom commun) (f)''' : ennemie.<br>
'''ἐχθρός, -ά, -όν (adjectif)''' : Détesté de ; ennemi de.<br>
'''ἐχθρός, -οῦ (nom commun) (m)''' : ennemi.<br>
'''ἐχθρότης, -τος (nom commun) (f)''' : inimitié.<br>
'''ἔχθω (verbe)''' : Haïr ; être hostile.<br>
'''ἔχιδνα, -ης (nom commun) (f)''' : vipère.<br>
'''ἐχῖνος, -ίνου (nom commun) (m)''' : hérisson.<br>
'''ἔχις, -εως (nom commun) (m)''' : vipère.<br>
'''ἔχω (verbe)''' : avoir.<br>
'''ἕψημα, -ήματος (nom commun) (f)''' : cuisson.<br>
'''ἕψησις, -ήσεως (nom commun) (f)''' : ébulition.<br>
'''ἒ ψιλόν (nom commun) (n)''' : epsilon.<br>
'''ἕψω (verbe)''' : Bouillir, cuire. Fondre, en parlant des métaux, raffiner, purifier. (Figuré) Être chaud pour ; chérir.<br>
'''ἕως (conjonction)''' : jusqu'à ce que ; tant que.<br>
'''Ἑϐραία, -ας (nom commun) (f)''' : Israélite.<br>
'''Ἑϐραῖος, -ίου (nom commun) (m)''' : Israélite.<br>
'''Ἑϐραΐς, -δος (nom commun) (f)''' : Israélite.<br>
'''Ἐδέμ (nom propre) (m)''' : Éden.<br>
'''Εἰλείθυια, -ίας (nom propre) (f)''' : [[wikt:Ilithyie|Ilithyie]] (déesse de l'enfantement).<br>
'''Εἰρηναῖος, -ίου (nom propre) (m)''' : Irénée.<br>
'''Εἰρήνη, -ης (nom propre) (f)''' : [[wikt:Eiréné|Eiréné]].<br>
'''Ἑκάϐη, -ης (nom propre) (f)''' : Hécube.<br>
'''Ἑκάτη, -ης (nom propre) (f)''' : Hécate.<br>
'''Ἑκατόγχειρες, -ων (nom propre) (m)''' : Hécatonchires.<br>
'''Ἕκτωρ, -ορος (nom propre) (m)''' : Hector.<br>
'''Ἐλεάζαρος, -άρου (nom propre) (m)''' : Éléazar.<br>
'''Ἐλαμείτης, -ου (nom commun) (m)''' : Élamite.<br>
'''Ἐλεύθυια, -ίας (nom propre) (f)''' : Forme ancienne de ''Εἰλείθυια''.<br>
'''Ἑλένη, -ης (nom propre) (f)''' : Hélène.<br>
'''Ἕλενος, -ένου (nom propre) (m)''' : Hélénos.<br>
'''Ἐλισάϐετ (nom propre) (f)''' : Élisabeth.<br>
'''Ἑλλάς, -δος (nom propre) (f)''' : Grèce.<br>
'''Ἕλλη, -ης (nom propre) (f)''' : Hellé.<br>
'''Ἕλλην, -ος (nom propre) (m)''' : Hellen.<br>
'''Ἕλλην, -ος (nom commun) (m)''' : Grec.<br>
'''Ἑλληνίς, -δος (nom commun) (f)''' : Grecque.<br>
'''Ἑλλήσποντος, -όντου (nom commun) (m)''' : Helléspont.<br>
'''Ἐμπεδοκλῆς, -έους (nom propre) (m)''' : Empédocle.<br>
'''Ἐνάρετη, -ης (nom propre) (f)''' : Énarété.<br>
'''Ἐνδυμίων, -ωνος (nom propre) (m)''' : Endymion.<br>
'''Ἐνετός, -οῦ (nom commun) (m)''' : Vénète.<br>
'''Ἐνυώ, -οῦς (nom propre) (f)''' : [[wikt:Ényo|Ényo]].<br>
'''Ἐπαμεινώνδας, -ου (nom commun) (m)''' : Épaminondas.<br>
'''Ἐπίκτητος, -ήτου (nom propre) (m)''' : Épictète.<br>
'''Ἐπιμηθεύς, -έως (nom propre) (m)''' : Épiméthée.<br>
'''Ἐπιφί (nom propre) (m)''' : Epiphi.<br>
'''Ἐρατοσθένης, -ους (nom propre) (m)''' : Ératosthène. (Savant grec né en -276 et mort en -194.)<br>
'''Ἐρατώ, -οῦς (nom propre) (f)''' : Érato.<br>
'''Ἔρεϐος, -έϐους (nom propre) (n)''' : [[wikt:Érèbe|Érèbe]].<br>
'''Ἐρεχθεύς, -έως (nom propre) (m)''' : Érechtée.<br>
'''Ἐρινύς, -ος (nom propre) (f)''' : Érynie.<br>
'''Ἔρις, -εως (nom propre) (f)''' : [[wikt:Éris|Éris]].<br>
'''Ἐριφύλη, -ης (nom propre) (f)''' : Ériphyle.<br>
'''Ἑρμᾶς, -οῦ (nom propre) (m)''' : Forme dorienne de ''Ἑρμῆς''.<br>
'''Ἑρμαφρόδιτος, -ίτου (nom propre) (m)''' : Hermaphrodite.<br>
'''Ἑρμῆς, -οῦ (nom propre) (m)''' : [[wikt:Hermès|Hermès]].<br>
'''Ἑρμιονεύς, -έως (nom propre) (m)''' : Hermionée.<br>
'''Ἑρμιόνη, -ης (nom propre) (f)''' : Hermione.<br>
'''Ἑρμιονίς, -δος (nom propre) (f)''' : Hermionide.<br>
'''Ἑρμιών, -ῶνος (nom propre) (m)''' : Hermion.<br>
'''Ἑρμογένης, -ους (nom propre) (m)''' : Hermogénès.<br>
'''Ἐρυσίχθων, -ονος (nom propre) (m)''' : Érysichthon.<br>
'''Ἔρως, -τος (nom propre) (m)''' : [[wikt:Éros|Éros]].<br>
'''Ἑσπερία, -ας (nom propre) (f)''' : Hespérie.<br>
'''Ἑσπερίδες, -ων (nom propre) (f)''' : Hespérides.<br>
'''Ἑσπερίς, -δος (nom propre) (f)''' : Hespéris.<br>
'''Ἕσπερος, -έρου (nom propre) (m)''' : Hespéros.<br>
'''Εὔα, -ας (nom propre) (f)''' : Ève.<br>
'''Εὐάγριος, -ίου (nom propre) (m)''' : Évagre.<br>
'''Εὐγενία, -ας (nom propre) (f)''' : Eugénie.<br>
'''Εὐγένιος, -ίου (nom propre) (m)''' : Eugène.<br>
'''Εὔδημος, -ήµου (nom propre) (m)''' : Eudème.<br>
'''Εὔδων, - (nom propre) (m)''' : Eudon (fleuve de Phrygie).<br>
'''Εὔιος, -ίου (nom propre) (m)''' : Euïos.<br>
'''Εὐλαλία, -ας (nom propre) (f)''' : Eulalie.<br>
'''Εὐλάλιος, -ίου (nom propre) (m)''' : Eulalios.<br>
'''Εὔμαιος, -ίου (nom propre) (m)''' : Eumée. (Porcher de Laërte et d’Ulysse.)<br>
'''Εὐήμερος, -έρου (nom propre) (m)''' : Évhémère.<br>
'''Εὔμηλος, -ήλου (nom propre) (m)''' : Eumélos.<br>
'''Εὔνηος, -ήου (nom propre) (m)''' : Eunée.<br>
'''Εὐνομία, -ας (nom propre) (f)''' : Eunomie.<br>
'''Εὐήρης, -ου (nom propre) (m)''' : Évérès.<br>
'''Εὐριπίδης, -ου (nom propre) (m)''' : Euripide.<br>
'''Εὔρος, -ου (nom propre) (m)''' : Euros. (dieu du vent d'est)<br>
'''Εὐρυάλη, -ης (nom propre) (f)''' : Euryale.<br>
'''Εὐρυδίκη, -ης (nom propre) (f)''' : Eurydice.<br>
'''Εὐρύθεμις, -έμιδας (nom propre) (f)''' : Eurythémis.<br>
'''Εὐρύκλεια, -ίας (nom propre) (f)''' : Euryclée.<br>
'''Εὐρύλοχος, -όχου (nom propre) (m)''' : Euryloque.<br>
'''Εὐρυσθεύς, -έως (nom propre) (m)''' : Eurysthée (fils de Sthénélos et Nicipée).<br>
'''Εὐρωπαῖος, -ίου (nom commun) (m)''' : Européen.<br>
'''Εὐρώπη, -ης (nom propre) (f)''' : Europe.<br>
'''Εὐσέϐεια, -ας (nom propre) (f)''' : Eusébie.<br>
'''Εὐσέϐιος, -ίου (nom propre) (m)''' : Eusèbe.<br>
'''Εὐστάθιος, -ίου (prénom) (m)''' : Eustache.<br>
'''Εὔσταχυς, -άχυος (prénom) (m)''' : Eustache.<br>
'''Εὐτέρπη, -ης (nom propre) (f)''' : Euterpe.<br>
'''Εὐτύχιος, -ίου (nom propre) (m)''' : Eutychius.<br>
'''Εὐφημία, -ας (nom propre) (f)''' : Euphémie.<br>
'''Εὔφημος, -ήμου (nom propre) (m)''' : Euphémos.<br>
'''Εὐφράτης, -ου (nom propre) (m)''' : Euphrate (fleuve de Turquie et d’Irak).<br>
'''Εὐφροσύνη, -ης (nom propre) (f)''' : Euphrosyne.<br>
'''Εὔφρων, -ονος (nom propre) (m)''' : Euphron.<br>
'''Ἔφεσος, -έσου (nom propre) (f)''' : Éphèse.<br>
'''Ἐφιάλτης, -ου (nom propre) (m)''' : Éphialtès.<br>
'''Ἕως, -ω (nom propre) (f)''' : Forme alternative de ''Ἠώς''.<br>
'''Ἑωσφόρος, -ου (nom propre) (m)''' : Lucifer.<br>
==Ζ==
'''ζῆλος, -ήλου (nom commun) (m)''' : Jalousie ; zèle.<br>
'''ζηλοτυπία, -ας (nom commun) (m)''' : jalousie.<br>
'''ζηλότυπος, -ος, -ον (adjectif)''' : jaloux.<br>
'''ζηλωτής, -οῦ (nom commun) (m)''' : zélote.<br>
'''ζηλῶ (verbe)''' : aimer ardemment.<br>
'''ζῆτα (nom commun) (n)''' : zêta.<br>
'''ζήτησις, -ήσεως (nom commun) (f)''' : recherche.<br>
'''ζητητικός, -ός, -όν (adjectif)''' : aimant chercher ; rechercher.<br>
'''ζητῶ (verbe)''' : demander ; chercher.<br>
'''ζιγγίϐερις, -έρεως (nom commun) (f)''' : gingembre.<br>
'''ζιζάνιον, -ίου (nom commun) (n)''' : ivraie.<br>
'''ζιζανοσπορεύς, -έως (nom commun) (m)''' : .<br>
'''ζιζανιοσπόρος, -ου (nom commun) (m)''' : .<br>
'''ζιζανώδης, -ης, -ες (adjectif)''' : .<br>
'''ζίζυφον, -ύϕου (nom commun) (n)''' : jujubier.<br>
'''ζόα, -ας (nom commun) (f)''' : Forme dorienne de ''ζωή''.<br>
'''ζόη, -ης (nom commun) (f)''' : Forme ionienne de ''ζωή''.<br>
'''ζοΐα, -ας (nom commun) (f)''' : Forme éolienne de ''ζωή''.<br>
'''ζόρξ, -κος (nom commun) (m)''' : Forme de ''δορκάς''.<br>
'''ζυγαριά, -ᾶς (nom commun) (f)''' : balance.<br>
'''ζυγόν, -οῦ (nom commun) (n)''' : joug.<br>
'''ζῦθος, -ύθου (nom commun) (m)''' : bière.<br>
'''ζωά, -ᾶς (nom commun) (f)''' : Autre forme dorienne de ''ζωή''.<br>
'''ζωή, -ῆς (nom commun) (f)''' : vie.<br>
'''ζῶμα, -ώματος (nom commun) (n)''' : caleçon.<br>
'''ζώμευμα, -ύματος (nom commun) (n)''' : soupe.<br>
'''ζωμεύω (verbe)''' : bouillir en soupe.<br>
'''ζωμήρυσις, -ύσεως (nom commun) (f)''' : louche à soupe.<br>
'''ζωμίδιον, -ίου (nom commun) (n)''' : sauce.<br>
'''ζωμός, -οῦ (nom commun) (m)''' : bouillon, soupe.<br>
'''ζωός, -ή, -όν (adjectif)''' : vivant.<br>
'''ζῷον, -ῴου (nom commun) (n)''' : animal.<br>
'''ζωοπανήγυρις, -ύρεως (nom commun) (f)''' : .<br>
'''ζῶ (verbe)''' : vivre.<br>
'''Ζαϐουλών (nom propre) (m)''' : Zabulon.<br>
'''Ζαγρεύς, -έως (nom propre) (m)''' : Zagreus.<br>
'''Ζακχαῖος, -ίου (nom propre) (m)''' : Zachée.<br>
'''Ζάν, -ός (nom propre) (m)''' : Forme dorienne de ''Ζεύς''.<br>
'''Ζάς, -νος (nom propre) (m)''' : Autre forme dorienne de ''Ζεύς''.<br>
'''Ζαχαρίας, -ου (nom propre) (m)''' : Zacharie.<br>
'''Ζεϐεδαῖος, -ίου (nom propre) (m)''' : Zébédée.<br>
'''Ζευξίππη, -ης (nom propre) (f)''' : Zeuxippe.<br>
'''Ζεῦξις, -ύξεως (nom propre) (m)''' : Zeuxis.<br>
'''Ζεῦς, -ύσεως (nom propre) (m)''' : Forme lesbienne de ''Ζεύς''.<br>
'''Ζεύς, Διός (nom propre) (m)''' : [[wikt:Zeus|Zeus]].<br>
'''Ζέφυρος, -ύρου (nom propre) (m)''' : [[wikt:Zéphyr|Zéphyr]]. (dieu du vent d'ouest)<br>
'''Ζῆλος, -ήλου (nom propre) (m)''' : Zélos.<br>
'''Ζήν, -ός (nom propre) (m)''' : Forme poétique de ''Ζεύς''.<br>
'''Ζηνοϐία, -ας (nom propre) (f)''' : Zénobie.<br>
'''Ζηνοϐίος, -ίου (nom propre) (m)''' : Zénobios.<br>
'''Ζυγός, -οῦ (nom propre) (m)''' : Balance.<br>
'''Ζωροάστρης, -ου (nom propre) (m)''' : Zoroastre.<br>
==Η==
'''ἡ (article défini)''' : la.<br>
'''ἤ (conjonction)''' : ou.<br>
'''ᾗ (adverbe relatif)''' : par là où (je passe).<br>
'''ἥϐη, -ης (nom commun) (f)''' : jeunesse.<br>
'''ἡϐητέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἡϐητής''.<br>
'''ἡϐητέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἡϐητής''.<br>
'''ἡϐητής, -ής, -ές (adjectif)''' : juvénile.<br>
'''ἡϐητῶς (adverbe)''' : juvénilement.<br>
'''ἡϐός, -ή, -όν (adjectif)''' : jeune.<br>
'''ἡϐότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἡϐός''.<br>
'''ἡϐότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἡϐός''.<br>
'''ἡγείσθαι (verbe)''' : conduire.<br>
'''ἡγεμονία, -ας (nom commun) (f)''' : Action de conduire, de diriger. Suprématie, puissance. Règne.<br>
'''ἡγεμονεύω (verbe)''' : .<br>
'''ἡγεμών, -όνος (nom commun) (m)''' : souverain.<br>
'''ἡγέομαι (verbe)''' : Marcher devant. (après Homère) Croire, penser.<br>
'''ἡγέτης, -ου (nom commun) (m)''' : leader.<br>
'''-ηγός, -οῦ (suffixe) (m/f)''' : .<br>
'''ἥδομαι (verbe)''' : avoir plaisir à, se réjouir de.<br>
'''ἡδονή, -ῆς (nom commun) (f)''' : plaisir.<br>
'''ἡδύς, -εῖα, -ύ (adjectif)''' : doux, agréable.<br>
'''ἡδύχρουν, - (nom commun) (n)''' : .<br>
'''ἠθικός, -ή, -όν (adjectif)''' : moral.<br>
'''ἠθικότατα, -, - (adverbe)''' : Superlatif de ''ἠθικῶς''.<br>
'''ἠθικότερον, -, - (adverbe)''' : Comparatif de ''ἠθικῶς''.<br>
'''ἠθικῶς (adverbe)''' : moralement.<br>
'''ἠθικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἠθικός''.<br>
'''ἠθικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἠθικός''.<br>
'''ἦθος, ἤθους (nom commun) (n)''' : us et coutumes.<br>
'''ἠϊθέη, -ης (nom commun) (f)''' : célibataire.<br>
'''ἠΐθεος, -έου (nom commun) (m)''' : célibataire.<br>
'''ἠι (adverbe)''' : Forme béotienne de ''ἀεί''.<br>
'''-ήϊον, -ΐου (suffixe)''' : Forme ionienne de ''-εῖον''.<br>
'''ἥκω (verbe)''' : être présent, être là.<br>
'''ἠλακάτη, -ης (nom commun) (f)''' : quenouille.<br>
'''ἤλεκτρον, -έκτρου (nom commun) (n)''' : ambre jaune.<br>
'''ἠλέκτωρ, -, - (adjectif)''' : brillant.<br>
'''ἡλιακός, -ή, -όν (adjectif)''' : solaire.<br>
'''ἤλιθιος, -η, -ον (adjectif)''' : stupide.<br>
'''ἡλικία, -ας (nom commun) (f)''' : âge.<br>
'''ἥλιος, -ίου (nom commun) (m)''' : soleil.<br>
'''ἠέλιος, -ίου (nom commun) (m)''' : Forme homérique de ''ἥλιος''.<br>
'''ἡλιοστάσιον, -ίου (nom commun) (n)''' : solstice.<br>
'''ἧλιξ, ἥλικος (nom commun) (m/f)''' : camarade.<br>
'''ἧλος, ἥλου (nom commun) (m)''' : Clou, tête de clou. Cal, durillon, galle.<br>
'''ἧμαι (verbe)''' : être assis.<br>
'''ἡμεῖς (pronom personnel) (m)''' : nous.<br>
'''ἡμέρα, -ας (nom commun) (f)''' : jour.<br>
'''ἡμέρα Ἡλίου (nom commun) (f)''' : dimanche.<br>
'''ἡμέρα Σελήνης (nom commun) (f)''' : lundi.<br>
'''ἡμέρα Ἄρεως (nom commun) (f)''' : mardi.<br>
'''ἡμέρα Ἕρμου (nom commun) (f)''' : mercredi.<br>
'''ἡμέρα Διός (nom commun) (f)''' : jeudi.<br>
'''ἡμέρα Ἀφροδίτης (nom commun) (f)''' : vendredi.<br>
'''ἡμέρα Κρόνου (nom commun) (f)''' : samedi.<br>
'''ἡμέρη, -ης''' (nom commun) (f) Forme homérique et ionienne de ''ἡμέρα''.<br>
'''ἡμερήσιος, -ια, -ον (adjectif)''' : journalier ; diurne.<br>
'''ἡμερολόγιον, -ίου (nom commun) (n)''' : calendrier.<br>
'''ἡμέτερος, -έρα, -έτερον (adjectif possessif)''' : notre.<br>
'''ἡμι- (préfixe)''' : à moitié.<br>
'''ἡμίκλαστος, -, - (adjectif)''' : .<br>
'''ἡμικρανία, -ας (nom commun) (f)''' : migraine.<br>
'''ἡμίονος, -όνου (nom commun) (m)''' : hémione.<br>
'''ἡνία, -ας (nom commun) (f)''' : .<br>
'''ἡνίοχος, -όχου (nom commun) (m)''' : cocher.<br>
'''ἧπαρ, ἥπατος (nom commun) (n)''' : foie.<br>
'''ἡπατικός, -ή, -όν (adjectif)''' : hépatique.<br>
'''ἡπατῖτις, -ίτης (nom commun) (f)''' : hépatite.<br>
'''ἤπειρος, -ίρου (nom commun) (f)''' : continent.<br>
'''ἠπειρωτικός, -ή, -όν (adjectif)''' : continental.<br>
'''ἡράκλειος, -α, -ον (adjectif)''' : herculéen.<br>
'''ἡρωίνη, -ης (nom commun) (f)''' : héroïne.<br>
'''ἡρωικός, -ή, -όν (adjectif)''' : héroïque.<br>
'''ἡρωικότατα, -, - (adverbe)''' : Superlatif de ''ἡρωικῶς''.<br>
'''ἡρωικότερον, -, - (adverbe)''' : Comparatif de ''ἡρωικῶς''.<br>
'''ἡρωικῶς (adverbe)''' : héroïquement.<br>
'''ἡρωικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἡρωικός''.<br>
'''ἡρωικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἡρωικός''.<br>
'''ἡρωισμός, -οῦ (nom commun) (m)''' : héroïsme.<br>
'''ἡρωίς, -δος (nom commun) (f)''' : héroïne.<br>
'''ἥρως, -ος (nom commun) (m)''' : héros.<br>
'''ἧσσα, ἥσσης (nom commun) (f)''' : défaite.<br>
'''ἡσσῶμαι (verbe)''' : être défait.<br>
'''ἡσυχάζω (verbe)''' : être en paix, garder le silence.<br>
'''ἡσυχασμός, -οῦ (nom commun) (m)''' : hésychasme.<br>
'''ἡσυχία, -ας (nom commun) (f)''' : Immobilité. Repos, calme ; silence.<br>
'''ἥσυχος, -ος, -ον (adjectif)''' : Calme, tranquille.<br>
'''ἡσύχως (adverbe)''' : Calmement, tranquillement.<br>
'''ἡσυχώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἥσυχος''.<br>
'''ἡσυχώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἥσυχος''.<br>
'''σωματικώτατα, -, - (adverbe)''' : Superlatif de ''ἡσύχως''.<br>
'''σωματικώτερον, -, - (adverbe)''' : Comparatif de ''ἡσύχως''.<br>
'''ἦτα (nom commun) (n)''' : êta.<br>
'''ἧτρον, ἥτρου (nom commun) (n)''' : abdomen.<br>
'''ἧττα, ἥττης (nom commun) (f)''' : Forme attique de ''ἧσσα''.<br>
'''ἡττῶμαι (verbe)''' : Forme attique de ''ἡσσῶμαι''.<br>
'''ἡφαιστεῖον, -ίου (nom commun) (m)''' : volcan.<br>
'''ἦχος, ἤχου (nom commun) (m)''' : son.<br>
'''ἠχώ, -οῦς (nom commun) (f)''' : son répercuté.<br>
'''ἠώς, -οῦς (nom commun) (f)''' : aurore.<br>
'''Ἥϐη, -ης (nom propre) (f)''' : [[wikt:Hébé|Hébé]].<br>
'''Ἡγήσιππος, -ίππου (nom propre) (m)''' : Hégésippe.<br>
'''Ἡγησώ, -οῦς (nom propre) (f)''' : Hègèsô.<br>
'''Ἠλίας, -α (nom propre) (m)''' : Élie.<br>
'''Ἥλιος, -ίου (nom propre) (m)''' : [[wikt:Hélios|Hélios]].<br>
'''Ἠλύσιον, -ίου (nom propre) (n)''' : Élysée. (Séjour des hommes vertueux après leur mort.)<br>
'''Ἡμέρα, -ας (nom propre) (f)''' : [[wikt:Héméra|Héméra]].<br>
'''Ἤπειρος, -ίρου (nom propre) (f)''' : Épire.<br>
'''Ἠπιδανός, -οῦ (nom propre) (m)''' : Forme ionienne d<nowiki>'</nowiki>''Ἀπιδανός''.<br>
'''Ἥρα, -ας (nom propre) (f)''' : [[wikt:Héra|Héra]].<br>
'''Ἡρακλέης, -ους (nom propre) (m)''' : Forme poétique de ''Ἡρακλῆς''.<br>
'''Ἡρακλῆς, -έους (nom propre) (m)''' : Héraclès.<br>
'''Ἡρόστρατος, -άτου (nom propre) (m)''' : Hérostrate.<br>
'''Ἠσαΐας, -ου (nom propre) (m)''' : Isaïe.<br>
'''Ἡσαΐας, -ου (nom propre) (m)''' : Forme alternative de ''Ἠσαΐας''.<br>
'''Ἡσαῦ (nom propre) (m)''' : Ésaü.<br>
'''Ἠσαῦ (nom propre) (m)''' : Forme alternative de ''Ἡσαῦ''.<br>
'''Ἡσύχιος, -ίου (nom propre) (m)''' : Hésychios.<br>
'''Ἥφαιστος, -ίστου (nom propre) (m)''' : [[wikt:Héphaïstos|Héphaïstos]].<br>
'''Ἠχώ, -οῦς (nom propre) (f)''' : [[wikt:Écho|Écho]].<br>
'''Ἠώς, -οῦς (nom propre) (f)''' : [[wikt:Éos|Éos]].<br>
==Θ==
'''θαλάμη, -ης (nom commun) (f)''' : Gîte ; tanière.<br>
'''θάλαμος, -άμου (nom commun) (m)''' : chambre.<br>
'''θάλασσα, -ης (nom commun) (f)''' : mer.<br>
'''θάλαττα, -ης (nom commun) (f)''' : Forme attique de ''θάλασσα''.<br>
'''θάμϐος, -ους (nom commun) (n)''' : stupeur.<br>
'''θανατηφόρος, -ος, -ον (adjectif)''' : mortifère.<br>
'''θάνατος, -άτου (nom commun) (m)''' : mort.<br>
'''θανάτωσις, -ώσεως (nom commun) (f)''' : .<br>
'''θανατῶ (verbe)''' : .<br>
'''θανών, -ών, -όν (adjectif)''' : mort.<br>
'''θάπτω (verbe)''' : enterrer.<br>
'''θαυμάζω (verbe)''' : être stupéfait.<br>
'''θαῦμα, -ύατος (nom commun) (n)''' : objet d’admiration ou d’étonnement ; merveille.<br>
'''θαυμαστός, -ή, -όν (adjectif)''' : merveilleux.<br>
'''θαυματουργία, -ας (nom commun) (f)''' : tour de force ; création de merveilles.<br>
'''θαυματουργός, -ός, -όν (adjectif)''' : Qui crée des merveilles.<br>
'''θαυματουργός, -οῦ (nom commun) (m)''' : créateur de merveilles.<br>
'''θαυματουργῶ (verbe)''' : créer des merveilles.<br>
'''θεά, -ᾶς (nom commun) (f)''' : déesse.<br>
'''θέα, -ας (nom commun) (f)''' : contemplation.<br>
'''θέαμα, -άματος (nom commun) (n)''' : spectacle.<br>
'''θεατής, -οῦ (nom commun) (m)''' : spectateur.<br>
'''θεατρικός, -ή, -όν (adjectif)''' : théâtral.<br>
'''θεατρικῶς (adverbe)''' : théâtralement.<br>
'''θεατρικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''θεατρικός''.<br>
'''θεατρικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''θεατρικός''.<br>
'''θεατρικώτατα, -, - (adverbe)''' : Superlatif de ''θεατρικῶς''.<br>
'''θεατρικώτερον, -, - (adverbe)''' : Comparatif de ''θεατρικῶς''.<br>
'''θέατρον, -ου (nom commun) (n)''' : théâtre.<br>
'''θεατρινισμός, -οῦ (nom commun) (m)''' : .<br>
'''θεατρινίστικος, -η, -ον (adjectif)''' : .<br>
'''θεατρίνος, -ου (nom commun) (m)''' : acteur.<br>
'''θέημα, -ήματος (nom commun) (n)''' : Forme ionienne de ''θέαμα''.<br>
'''θέητρον, -ου (nom commun) (n)''' : Forme ionienne de ''θέατρον''.<br>
'''θεῖα, -ίας (nom commun) (f)''' : tante.<br>
'''θεῖον, -ίου (nom commun) (m)''' : soufre ; fumée de soufre.<br>
'''θεῖος, -ίου (nom commun) (m)''' : oncle.<br>
'''θέειος, -ίου (nom commun) (m)''' : Forme homérique de ''θεῖος''.<br>
'''θεήϊος, -ΐου (nom commun) (m)''' : Forme de ''θεῖος''.<br>
'''-θέτης, -ου (suffixe) (m)''' : .<br>
'''θέλγω (verbe)''' : fasciner.<br>
'''θέμα, -τος (nom commun) (n)''' : Dépôt, ce qui est placé, posé ou déposé. Endroit où l’on pose ou place quelque chose. .<br>
'''θεματίζω (verbe)''' : déposer.<br>
'''θεματίτης, -ου (nom commun) (m)''' : dépositaire.<br>
'''θεματικός, -ή, -όν (adjectif)''' : thématique.<br>
'''θέμις, -τος (nom commun) (f)''' : loi divine ou morale.<br>
'''θέογνις, -δος (nom commun) (m)''' : .<br>
'''θεός, -οῦ (nom commun) (m)''' : dieu.<br>
'''θεραπεία, -ας (nom commun) (f)''' : soin.<br>
'''θεραπευτήριον, -ίου (nom commun) (n)''' : sanatorium.<br>
'''θεραπευτής, -οῦ (nom commun) (m)''' : soignant.<br>
'''θεραπευτικός, -ή, -όν (adjectif)''' : attentif, serviable ; curatif.<br>
'''θεραπεύω (verbe)''' : soigner ; prendre soin de.<br>
'''θεράπων, -οντος (nom commun) (m)''' : serviteur.<br>
'''θερμόν, -οῦ (nom commun) (n)''' : chaleur.<br>
'''θερμός, -ή, -όν (adjectif)''' : chaud.<br>
'''θερμότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''θερμός''.<br>
'''θερμότερος, -έρη, -ότατον (adjectif)''' : Comparatif de ''θερμός''.<br>
'''θερμῶς (adverbe)''' : chaudement.<br>
'''θέρος, -ους (nom commun) (n)''' : été.<br>
'''θέρω (verbe)''' : chauffer.<br>
'''θέσις, -εως (nom commun) (f)''' : .<br>
'''θεσμός, -οῦ (nom commun) (m)''' : (Primitivement) Toute loi ou institution établie par les dieux, institution sacrée, rite, coutume antique. Loi divine ou naturelle, par opposition à la loi écrite. (Par extension) Loi faite par les hommes, loi écrite.<br>
'''θεσμοθέτης, -ου (nom commun) (m)''' : parlementaire.<br>
'''θεσμοθετεῖον, -ίου (nom commun) (n)''' : parlement.<br>
'''θεύς, -έως (nom commun) (m)''' : Forme dorienne de ''θεός''.<br>
'''θεῶμαι (verbe)''' : Regarder, contempler (le plus souvent avec étonnement ou admiration), admirer. Contempler en esprit. Voir clairement. Être spectateur. Revoir, passer en revue, examiner.<br>
'''θεώρημα, -ήματος (nom commun) (n)''' : Spectacle, fête ; règle, principe, objet d'étude ou de méditation ; contemplation, recherche.<br>
'''θεωρητικός, -ή, -όν (adjectif)''' : théorique.<br>
'''θεωρητικῶς (adverbe)''' : théoriqument.<br>
'''θεωρητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''θεωρητικός''.<br>
'''θεωρητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''θεωρητικός''.<br>
'''θεωρητικώτατα, -, - (adverbe)''' : Superlatif de ''θεωρητικῶς''.<br>
'''θεωρητικώτερον, -, - (adverbe)''' : Comparatif de ''θεωρητικῶς''.<br>
'''θεωρία, -ας (nom commun) (f)''' : Observation, vue, action de voir. Spectacle, ce qui est vu. Mission diplomatique. État de spectateur, audience.<br>
'''θεωρός, -οῦ (nom commun) (m)''' : Spectateur, observateur.<br>
'''θεωρῶ (verbe)''' : Observer, examiner, contempler. Inspecter, passer en revue. Contempler. (Figuré) Contempler par l’intelligence.<br>
'''θέω (verbe)''' : courir.<br>
'''θήϊος, -ΐου (nom commun) (m)''' : Forme éolienne de ''θεῖος''.<br>
'''θήκη, -ης (nom commun) (f)''' : Boîte, coffre. Cercueil, tombeau.<br>
'''θήλεια, - (nom commun) (f)''' : .<br>
'''θηλυκός, -ή, -όν (adjectif)''' : féminin.<br>
'''θῆλυ, -ήλεας (nom commun) (f)''' : femelle.<br>
'''θῆλυς, -ήλεια, -ῆλυ (adjectif)''' : femelle.<br>
'''θηλύτης, -τος (nom commun) (f)''' : féminité.<br>
'''θηλῶ (verbe)''' : .<br>
'''θῆμα, -ήματος (nom commun) (n)''' : tombe.<br>
'''θήρα, -ας (nom commun) (f)''' : chasse.<br>
'''θήραμα, -άματος (nom commun) (n)''' : proie.<br>
'''θηρευτής, -οῦ (nom commun) (m)''' : chasseur.<br>
'''θηρευτικός, -ή, -όν (adjectif)''' : de chasse.<br>
'''θηριότης, -τας (nom commun) (f)''' : monstruosité.<br>
'''θηριώδης, -ης, -ες (adjectif)''' : monstrueux.<br>
'''θηριωδία, -ας (nom commun) (f)''' : monstruosité.<br>
'''θηρίον, -ου (nom commun) (n)''' : animal sauvage.<br>
'''θήρ, -ός (nom commun) (m)''' : bête sauvage.<br>
'''θηρῶ (verbe)''' : chasser.<br>
'''θησαυρός, -οῦ (nom commun) (m)''' : trésor.<br>
'''θῆσσα, -ήσσα (nom commun) (f)''' : serve.<br>
'''θής, -τός (nom commun) (m)''' : serf.<br>
'''θῆτα (nom commun) (n)''' : thêta.<br>
'''θητεία, -ας (nom commun) (f)''' : service.<br>
'''θητεύω (verbe)''' : asservir.<br>
'''θῆττα, -ήττα (nom commun (f)''' : Forme attique de ''θῆσσα''.<br>
'''θιγγάνω (verbe)''' : toucher.<br>
'''θιός, -οῦ (nom commun) (m)''' : Forme béotienne et arcado-chypriote de ''θεός''.<br>
'''θλάσις, -εως (nom commun) (f)''' : contusion.<br>
'''θλιϐή, -ῆς (nom commun) (f)''' : .<br>
'''θλίϐω (verbe)''' : Presser ; compresser, réduire. Oppresser, attrister.<br>
'''θλῖψις, -ίψεως (nom commun) (f)''' : Pression, compression ; oppression.<br>
'''θλίψις, -εως (nom commun) (f)''' : chagrin, tristesse.<br>
'''θλῶ (verbe)''' : étendre.<br>
'''θναίσκω (verbe)''' : Forme éolienne de ''θνῄσκω''.<br>
'''θνᾴσκω (verbe)''' : Forme dorienne de ''θνῄσκω''.<br>
'''θνῄσκω (verbe)''' : mourir.<br>
'''θοάζω (verbe)''' : se hâter.<br>
'''θολερός, -ά, -όν (adjectif)''' : sale.<br>
'''θολερώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''θολερός''.<br>
'''θολερώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''θολερός''.<br>
'''θολερῶς (adverbe)''' : salement.<br>
'''θοός, -ή, -όν (adjectif)''' : rapide.<br>
'''θόρυϐος, -ύϐου (nom commun) (m)''' : bruit.<br>
'''θορυϐωδέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''θορυϐώδης''.<br>
'''θορυϐωδέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''θορυϐώδης''.<br>
'''θορυϐώδης, -ης, -ες (adjectif)''' : bruyant.<br>
'''θορυϐώδως (adverbe)''' : bruyamment.<br>
'''θοῶς (adverbe)''' : rapidement.<br>
'''θοώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''θοός''.<br>
'''θοώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''θοός''.<br>
'''θρᾴκιος, -ος, -ον (adjectif)''' : thrace.<br>
'''θρασέως (adverbe)''' : audacieusement.<br>
'''θράσος, -ους (nom commun) (n)''' : audace.<br>
'''θρασύς, -εῖα, -ύ (adjectif)''' : audacieux.<br>
'''θρασύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''θρασύς''.<br>
'''θρασύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''θρασύς''.<br>
'''θραῦσμα, -ύσματος (nom commun) (n)''' : fragment.<br>
'''θρασύτης, -τητος (nom commun) (f)''' : audace (qualité).<br>
'''θρῄκιος, -ος, -ον (adjectif)''' : Forme poétique de ''θρᾴκιος''.<br>
'''θρῆνος, -ήνου (nom commun) (m)''' : lamentation.<br>
'''θρηνῶ (verbe)''' : se lamenter.<br>
'''θρησκεία, -ας (nom commun) (n)''' : religion.<br>
'''θρησκεύω (verbe)''' : observer religieusement.<br>
'''θρῆσκος, -ήσκα, -ῆσκον (adjectif)''' : pieux.<br>
'''θρίαμϐος, -άμϐου (nom commun) (m)''' : triomphe.<br>
'''θρίξ, τριχός (nom commun) (f)''' : poil.<br>
'''θρίψ, -πός (nom commun) (m)''' : petite vrillette. (Au figuré) avare.<br>
'''θροέω (verbe)''' : froisser.<br>
'''θρόμϐος, -ου (nom commun) (m)''' : Grumeau, caillot.<br>
'''θρόνος, -ου (nom commun) (m)''' : siège élevé.<br>
'''θρόος, -ου (nom commun) (m)''' : froissement.<br>
'''θροῦς, -οῦ (nom commun) (m)''' : rumeur.<br>
'''θροῶ (verbe)''' : froisser.<br>
'''θρυλικός, -ή -όν (adjectif)''' : légendaire.<br>
'''θρῦλος, -ύλου (nom commun) (f)''' : légende. (Récit populaire, plus ou moins fabuleux.)<br>
'''θρύπτω (verbe)''' : casser ; morceler.<br>
'''θύαρος, -άρου (nom commun) (m)''' : ivraie.<br>
'''θυγάτηρ, -ρός (nom commun) (f)''' : fille. (descendante)<br>
'''θύλακος, -άκου (nom commun) (m)''' : bourse ; sac.<br>
'''θῦμα, -ύματος (nom commun) (n)''' : victime.<br>
'''θυμίαμα, -άματος (nom commun) (n)''' : encens.<br>
'''θυμιάω (verbe)''' : brûler.<br>
'''θυμίημα, -ήματος (nom commun) (n)''' : Forme ionienne de ''θυμίαμα''.<br>
'''θυμολέων, -ων, -ον (adjectif)''' : Courageux ; cœur-de-lion.<br>
'''θυμός, -οῦ (nom commun) (m)''' : Âme, souffle de vie. Esprit, cœur. Courage.<br>
'''θύμος, -ου (nom commun) (m)''' : thym.<br>
'''θύννος, -ου (nom commun) (m)''' : thon.<br>
'''θύρα, -ας (nom commun) (f)''' : Porte, battant ; ouverture, entrée.<br>
'''θυραωρός, -οῦ (nom commun) (m)''' : Forme homérique de ''θυρωρός''.<br>
'''θυρωρεῖον, -ίου (nom commun) (n)''' : conciergerie.<br>
'''θυρωρός, -οῦ (nom commun (m) : concierge.<br>
'''θύρσος, -ου (nom commun) (m)''' : thyrse.<br>
'''θύρωμα, -ώματος (nom commun) (n)''' : portail.<br>
'''θυσία, -ας (nom commun) (f)''' : sacrifice.<br>
'''θυσιάζω (verbe)''' : sacrifier.<br>
'''θωή, -ῆς (nom commun) (f)''' : .<br>
'''θῶμα, -ώματος (nom commun) (n)''' : Forme ionienne de ''θαῦμα''.<br>
'''θωμαστός, -ή, -όν (adjectif)''' : Forme ionienne de ''θαυμαστός''.<br>
'''θῶμιγξ, -ώμιγγος (nom commun) (f)''' : Corde ; ficelle.<br>
'''θωπεία, -ας (nom commun) (f)''' : caresse.<br>
'''θώπευμα, -ύματος (nom commun) (n)''' : caresse.<br>
'''θωπευτικός, -ή, -όν (adjectif)''' : caressant.<br>
'''θωπεύω (verbe)''' : caresser.<br>
'''θώς, -ός (nom commun) (m/f)''' : chacal.<br>
'''θώϋμα, -τος (nom commun) (n)''' : Autre forme ionienne de ''θαῦμα''.<br>
'''θώψ, -πός (nom commun) (m)''' : adulateur.<br>
'''Θαδδαῖος, -ίου (nom propre) (m)''' : Thadée.<br>
'''Θαΐς, -δος (nom propre) (f)''' : [[wikt:Thaïs|Thaïs]].<br>
'''Θάλεια, -ας (nom propre) (f)''' : Thalie. (muse)<br>
'''Θαλῆς, -άλεω (nom propre) (m)''' : Thalès.<br>
'''Θαλία, -ας (nom propre) (f)''' : Thalie. (Charite)<br>
'''Θάνατος, -άτου (nom propre) (m)''' : [[wikt:Thanatos|Thanatos]].<br>
'''Θαῦμας, -ύμαντος (nom propre) (m)''' : Thaumas.<br>
'''Θάψος, -ου (nom propre) (f)''' : Thapsus. (ville de Tunisie)<br>
'''Θέμις, -δος (nom propre) (f)''' : [[wikt:Thémis|Thémis]].<br>
'''Θεμιστοκλῆς, -έους (nom propre) (m)''' : Thémistocle.<br>
'''Θέτις, -δος (nom propre) (f)''' : [[wikt:Thétis|Thétis]].<br>
'''Θέογνις, -δος (nom propre) (m)''' : Théognis.<br>
'''Θεοδοσία, -ας (nom propre) (f)''' : Théodosia.<br>
'''Θεοδόσιος, -ίου (nom propre) (m)''' : Théodose.<br>
'''Θεόδουλος, -ύλος (nom propre) (m)''' : Théodule.<br>
'''Θεόδωρος, -ώρου (nom propre) (m)''' : Théodore.<br>
'''Θεόπομπος, -όμπου (nom propre) (m)''' : Théopompe.<br>
'''Θεός, -οῦ (nom propre) (m)''' : Dieu.<br>
'''Θεοφάνης, -ου (nom propre) (m)''' : Théophane.<br>
'''Θεοφανία, -ας (nom propre) (f)''' : Théophanie.<br>
'''Θεόφιλος, -ίλου (nom propre) (m)''' : Théophile.<br>
'''Θεόφραστος, -άστου (nom propre) (m)''' : Théophraste.<br>
'''Θερμοπύλαι, -ῶν (nom propre) (f)''' : Thermopyles.<br>
'''Θέσπις, -εως (nom propre) (m)''' : Thespsis.<br>
'''Θεσσαλία, -ας (nom propre) (f)''' : Thessalie.<br>
'''Θεσσαλικός, -ή, -όν (adjectif)''' : thessalien.<br>
'''Θεσσαλίς, -δος (nom commun) (f)''' : Thessalienne.<br>
'''Θεσσαλονίκη, -ης (nom propre) (f)''' : Thessalonique.<br>
'''Θεσσαλός, -οῦ (nom commun) (m)''' : Thessalien.<br>
'''Θευδᾶς, -ᾶ (nom propre) (m)''' : Theudas.<br>
'''Θευδέριχος, -ίχου (nom propre) (m)''' : Théodoric.<br>
'''Θῆϐαι, -ῶν (nom propre) (f)''' : Thèbes.<br>
'''Θησεύς, -έως (nom propre) (m)''' : Thésée.<br>
'''Θίνη, -ης (nom propre) (f)''' : Synonyme de ''Σίνη''.<br>
'''Θιός, -οῦ (nom propre) (m)''' : Forme béotienne de ''Ζεύς''.<br>
'''Θίσϐη, -ης (nom propre) (f)''' : Thisbé.<br>
'''Θόας, -αντος (nom propre) (m)''' : Thoas.<br>
'''Θούθμωσις, -ώσιδος (nom propre) (m)''' : Thoutmôsis.<br>
'''Θουκυδίδης, -ου (nom propre) (m)''' : Thucydide.<br>
'''Θόωσα, -ης (nom propre) (f)''' : Thoosa.<br>
'''Θρᾴκη, -ης (nom propre) (f)''' : Thrace.<br>
'''Θρᾷξ, -ᾳκός (nom propre) (m)''' : Thrax.<br>
'''Θρᾷξ, -ᾳκός (nom commun) (m)''' : Thrace.<br>
'''Θρᾷσσα, -ᾴσσης (nom commun) (f)''' : Thrace.<br>
'''Θρᾷττα, -ᾴττης (nom commun) (f)''' : Forme attique de ''Θρᾷσσα''.<br>
'''Θρῄκη, -ης (nom propre) (f)''' : Forme poétique de ''Θρᾴκη''.<br>
'''Θρῇξ, -ῃκός (nom commun) (m)''' : Forme poétique de ''Θρᾷξ''.<br>
'''Θρῇσσα, -ῄσσης (nom commun) (f)''' : Forme poétique de ''Θρᾷσσα''.<br>
'''Θωθ (nom propre) (m)''' : Thot.<br>
'''Θωμᾶς, -ᾶ (prénom) (m)''' : Thomas.<br>
==Ι==
'''ἴαμϐος, -άµϐου (nom commun) (m)''' : ïambe.<br>
'''ἰάομαι (verbe)''' : soigner.<br>
'''ἱαρός, -ά, -όν (adjectif)''' : Forme dorienne de ''ἱερός''.<br>
'''ἴασις, -άσεως (nom commun) (f)''' : cure ; remède.<br>
'''ἴασπις, -δος (nom commun) (f)''' : jaspe.<br>
'''ἰατήρ, -ῆρος (nom commun) (m)''' : guérisseur.<br>
'''ἰατός, -ός, -όν (adjectif)''' : curable ; soignable.<br>
'''ἰατρεία, -ας (nom commun) (f)''' : médecine.<br>
'''ἰατρικός, -ή, -όν (adjectif)''' : médical.<br>
'''ἰατρικῶς (adverbe)''' : médicalement.<br>
'''ἰατρικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἰατρικός''.<br>
'''ἰατρικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἰατρικός''.<br>
'''ἰατρικώτατα, -, - (adverbe)''' : Superlatif de ''ἰατρικῶς''.<br>
'''ἰατρικώτερον, -, - (adverbe)''' : Comparatif de ''ἰατρικῶς''.<br>
'''ἰατρός, -οῦ (nom commun) (m)''' : médecin.<br>
'''ἱϐίσκος, -ου (nom commun) (m)''' : guimauve.<br>
'''ἴγνης, -τος (nom commun) (m)''' : indigène.<br>
'''ἰγνύα, -ας (nom commun) (f)''' : jarret.<br>
'''ἰγνύη, -ης (suffixe) (f)''' : Forme hommérique et ionienne de ''ἰγνύα''.<br>
'''ἰγνύς, -ος (nom commun) (f)''' : Forme de ''''.<br>
'''ἴκτις, -δος (nom commun) (f)''' : martre.<br>
'''ἱρός, -ά, -όν (adjectif)''' : Forme ionienne de ''ἱερός''.<br>
'''ἷρος, -α, -ον (adjectif)''' : Forme éolienne de ''ἱερός''.<br>
'''-ία, -ας (suffixe) (f)''' : marque nominale.<br>
'''-ικός, -ή, -όν (suffixe)''' : marque adjectivale.<br>
'''-ικῶς (adverbe)''' : Forme adverbiale de ''-ικός''.<br>
'''-ικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''-ικός''.<br>
'''-ικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''-ικός''.<br>
'''-ικώτατα, -, - (adverbe)''' : Superlatif de ''-ικῶς''.<br>
'''-ικώτερον, -, - (adverbe)''' : Comparatif de ''-ικῶς''.<br>
'''-ίσκος, -ου (suffixe)''' : diminutif.<br>
'''-ιμος, -ος, -ον (suffixe)''' : .<br>
'''ἱαρεύς, -έως (nom commun) (m)''' : Forme dorienne de ''ἱερεύς''.<br>
'''ἰαῦ (interjection)''' : hé, ho.<br>
'''ἰαύω (verbe)''' : Dormir, se reposer.<br>
'''ἶϐις, ἴϐιδος (nom commun) (f)''' : ibis.<br>
'''ἰδίᾳ (adverbe)''' : en particulier, séparément, à part.<br>
'''ἰδιοσυγκρασία, -ας (nom commun) (f)''' : tempérament particulier.<br>
'''ἴδιος, -ία, -ιον (adjectif)''' : propre, particulier.<br>
'''ἰδιοφυής, -ής, -ές (adjectif)''' : génial.<br>
'''ἰδιοφυΐα, -ας (nom commun) (f)''' : génie (talent d’une personne).<br>
'''ἰδιώτης, -ου (nom commun) (m)''' : individu, plébéien ; ignorant.<br>
'''ἰδίω (verbe)''' : suer.<br>
'''ἵδρυμα, -ύματος (nom commun) (n)''' : institution.<br>
'''ἵδρυσις, -ύσεως (nom commun) (n)''' : fondation.<br>
'''ἱδρύω (verbe)''' : .<br>
'''ἱδρώς, -ῶτος (nom commun) (m)''' : sueur ; sudation. Sève ; jus, moisissure.<br>
'''ἱέραξ, -κου (nom commun) (m)''' : faucon.<br>
'''ἱερατεία, -ας (nom commun) (f)''' : tempérament particulier.<br>
'''ἱεράτευμα, -ύματος (nom commun) (n)''' : .<br>
'''ἱερατεύω (verbe)''' : .<br>
'''ἱερατικός, -ή, -όν (adjectif)''' : sacerdotal.<br>
'''ἱερατικῶς (adverbe)''' : sacerdotalement.<br>
'''ἱερατικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἱερατικός''.<br>
'''ἱερατικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἱερατικός''.<br>
'''ἱέρεια, -ίας (nom commun) (f)''' : prêtresse.<br>
'''ἱερεύς, -έως (nom commun) (m)''' : prêtre.<br>
'''ἱεροκῆρυξ, -ήρυκος (nom commun) (m)''' : prêcheur.<br>
'''ἱερόν, -οῦ (nom commun) (n)''' : sanctuaire.<br>
'''ἱεροσυλία, -ας (nom commun) (m)''' : sacrilège (action).<br>
'''ἱερόσυλος, -ύλου (nom commun) (m)''' : sacrilège (personne).<br>
'''ἱερός, -ά, -όν (adjectif)''' : sacré.<br>
'''ἱερῶς (adverbe)''' : sacrément.<br>
'''ἱερώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἱερός''.<br>
'''ἱερώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἱερός''.<br>
'''ἱεροσύνη, -ης (nom commun) (f)''' : sacerdoce.<br>
'''ἱερῶ (verbe)''' : .<br>
'''ἰή (interjection)''' : Youpi, hourra. Aïe.<br>
'''ἷημι (verbe)''' : envoyer, lancer.<br>
'''ἰητρός, -οῦ (nom commun) (m)''' : Forme homérique et ionienne de ''ἰατρός''.<br>
'''ἰθαγενής, -ής, -ές (adjectif)''' : indigène.<br>
'''ἰθακήσιος, -ος, -ον (adjectif)''' : ithacien.<br>
'''ἰθέως (adverbe)''' : Forme homérique et ionienne de ''εὐθέως''.<br>
'''ἰθύς, -εῖα, -ύ (adjectif)''' : Forme homérique et ionienne de ''εὐθύς''.<br>
'''ἰθύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''ἰθύς''.<br>
'''ἰθύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''ἰθύς''.<br>
'''ἰθύφαλλος, -άλλου (nom commun) (m)''' : ithyphalle.<br>
'''ἱκεσία, -ας (nom commun) (f)''' : supplique.<br>
'''ἱκετεύω (verbe)''' : supplier.<br>
'''ἱκέτις, -δος (nom commun) (f)''' : pèlerine.<br>
'''ἱκέτης, -ου (nom commun) (m)''' : pèlerin.<br>
'''ἴκκος, -ου (nom commun) (m/f)''' : Forme éolienne de ''ἵππος''.<br>
'''ἱκνέομαι (verbe)''' : aller à ; atteindre. Venir.<br>
'''ἴκρια, -ας (nom commun) (f)''' : .<br>
'''ἴκριον, -ου (nom commun) (n)''' : échafaudage.<br>
'''ἰκρίωμα, -ώματος (nom commun) (n)''' : échafaudage.<br>
'''ἰκριῶ (verbe)''' : .<br>
'''ἴκτις, -δος (nom commun) (f)''' : fouine.<br>
'''-ικός, -ή, -όν (adjectif)''' : -ique.<br>
'''-ικῶς (adverbe)''' : -ment.<br>
'''-ικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''-ικός''.<br>
'''-ικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''-ικός''.<br>
'''-ικώτατα, -, - (adverbe)''' : Superlatif de ''-ικῶς''.<br>
'''-ικώτερον, -, - (adverbe)''' : Comparatif de ''-ικῶς''.<br>
'''ἵκω (verbe)''' : venir.<br>
'''ἵλαος, -ος, -ον (adjectif)''' : propice (en parlant des dieux). Gentil, aimable (en parlant des hommes).<br>
'''ἱλαρός, -ά, -όν (adjectif)''' : joyeux, riant.<br>
'''ἱλαρότης, -τος (nom commun) (f)''' : hilarité.<br>
'''ἱλάσκομαι (verbe)''' : apaiser, calmer.<br>
'''ἴλεξ, -κος (nom commun) (f)''' : houx.<br>
'''ἰλιγγιωδέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἰλιγγιώδης''.<br>
'''ἰλιγγιωδέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''ἰλιγγιώδης''.<br>
'''ἰλιγγιώδης, -ης, -ες (adjectif)''' : vertigineux.<br>
'''ἰλιγγιωδῶς (adverbe)''' : vertigineusement.<br>
'''ἰλιγγιωδώτατα, -, - (adverbe)''' : Superlatif de ''ἰλιγγιωδῶς''.<br>
'''ἰλιγγιωδώτερον, -, - (adverbe)''' : Comparatif de ''ἰλιγγιωδῶς''.<br>
'''ἴλιγγος, -ίγγου (nom commun) (m)''' : vertige.<br>
'''ἰλλυρικός, -ή, -όν (adjectif)''' : illyrien.<br>
'''ἰλλυρικῶς (adverbe)''' : illyriennement.<br>
'''ἰλλυρικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἰλλυρικός''.<br>
'''ἰλλυρικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἰλλυρικός''.<br>
'''ἰλλυρικώτατα, -, - (adverbe)''' : Superlatif de ''ἰλλυρικῶς''.<br>
'''ἰλλυρικώτερον, -, - (adverbe)''' : Comparatif de ''ἰλλυρικῶς''.<br>
'''ἴλλω (verbe)''' : avoir le vertige.<br>
'''ἰλύς, -ος (nom commun) (f)''' : Boue, sédiment. Impureté.<br>
'''ἱμάς, -άντος (nom commun) (m)''' : courroie.<br>
'''ἱμάσσω (verbe)''' : fouetter, flageller ; frapper.<br>
'''ἱμάτιον, -ίου (nom commun) (n)''' : himation.<br>
'''ἵμερος, -έρου (nom commun) (n)''' : désir amoureux.<br>
'''ἱμῶ (verbe)''' : .<br>
'''ἰξευτής, -οῦ (nom commun) (m)''' : oiseleur.<br>
'''ἰξεύω (verbe)''' : oiseler.<br>
'''ἰξός, -οῦ (nom commun) (m)''' : Glu ; gui.<br>
'''ἰός, -οῦ (nom commun) (m)''' : Venin ; flèche.<br>
'''ἰουδαῖος, -ία, -ῖον (adjectif)''' : juif.<br>
'''ἴου (interjection)''' : beurk.<br>
'''ἰπνός, -οῦ (nom commun) (m)''' : four.<br>
'''ἱππάζομαι (verbe)''' : chevaucher.<br>
'''ἱππαλεκτρυών, -όνος (nom commun) (m/f)''' : hippalectryon.<br>
'''ἱππασία, -ας (nom commun) (f)''' : chevauchée.<br>
'''ἱππαστί (adverbe)''' : à cheval.<br>
'''ἱππεύς, -έως (nom commun) (m)''' : cavalier.<br>
'''ἱππευτής, -οῦ (nom commun) (m)''' : cavalier.<br>
'''ἱππεύω (verbe)''' : monter à cheval.<br>
'''ἱππικός, -ή, -όν (adjectif)''' : équin.<br>
'''ἱππικῶς (adverbe)''' : équinement.<br>
'''ἱππικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἱππικός''.<br>
'''ἱππικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἱππικός''.<br>
'''ἱππικώτατα, -, - (adverbe)''' : Superlatif de ''ἱππικῶς''.<br>
'''ἱππικώτερον, -, - (adverbe)''' : Comparatif de ''ἱππικῶς''.<br>
'''ἵππος, -ου (nom commun) (m/f)''' : cheval, jument.<br>
'''ἱππόκαμπος, -άμπου (nom commun) (m)''' : hippocampe.<br>
'''ἱπποσύνη, -ης (nom commun) (f)''' : Équitation. (Militaire) Cavalerie.<br>
'''ἱπποπόταμος, -άμου (nom commun) (m)''' : hippopotame.<br>
'''ἱππο- (préfixe)''' : relatif au cheval.<br>
'''ἱππότης, -ου (nom commun) (m)''' : cavalier.<br>
'''ἴορκος, -όρκος (nom commun) (m)''' : Forme de ''δορκάς''.<br>
'''ἰός, -οῦ (nom commun) (m)''' : Flèche ; venin.<br>
'''ἰοῦ (interjection)''' : hourra.<br>
'''ἱρεύς, -έως (nom commun) (m)''' : Forme ionienne de ''ἱερεύς''.<br>
'''ἵρηξ, -κος (nom commun) (m)''' : Forme ionienne de ''ἱέραξ''.<br>
'''ἶρις, ἴριδος (nom commun) (f)''' : (anatomie) iris (météorologie) arc-en-ciel.<br>
'''ἰσαίτατα, -, - (adverbe)''' : Superlatif de ''ἴσως''.<br>
'''ἰσαίτερον, -, - (adverbe)''' : Comparatif de ''ἴσως''.<br>
'''ἴσατις, -δος (nom commun) (f)''' : guède.<br>
'''ἰσημερία, -ας (nom commun) (f)''' : équinoxe.<br>
'''ἰσορροπία, -ας (nom commun) (f)''' : équilibre.<br>
'''ϝίσϝος, -η, -ον (adjectif)''' : Forme arcado-chypriote et crétoise de ''ἴσος''.<br>
'''ἰσθμός, -οῦ (nom commun) (m)''' : isthme.<br>
'''ἴσοξ, -κου (nom commun) (m)''' : brochet.<br>
'''ἶσος, -η, -ον (adjectif)''' : Forme homérique de ''ἴσος''.<br>
'''ἴσος, -η, -ον (adjectif)''' : égal.<br>
'''ἴς, -νός (nom commun) (f)''' : force, puissance ; muscle.<br>
'''ἵστημι (verbe)''' : Placer ; mettre debout.<br>
'''ἱστίον, -ου (nom commun) (n)''' : os de la hanche.<br>
'''ἱστόρημα, -ήματος (nom commun) (n)''' : narration.<br>
'''ἱστορία, -ας (nom commun) (f)''' : Enquête, examination ; observation, étude.<br>
'''ἱστορικός, -ή, -όν (adjectif)''' : D’enquête, bien informé.<br>
'''ἱστορῶ (verbe)''' : enquêter.<br>
'''ἱστός, -οῦ (nom commun) (m)''' : mât.<br>
'''ἵστωρ, -ορος (nom commun) (m/f)''' : Individu sachant. Personne connaissant la loi. Juge.<br>
'''ἰσχίον, -ου (nom commun) (n)''' : os de la hanche.<br>
'''ἰσχνός, -ή, -όν (adjectif)''' : Desséché, sec. Maigre, grêle ; frêle.<br>
'''ἰσχνότατα, -, - (adverbe)''' : Superlatif de ''ἰσχνῶς''.<br>
'''ἰσχνότερον, -, - (adverbe)''' : Comparatif de ''ἰσχνῶς''.<br>
'''ἰσχνότατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ἰσχνός''.<br>
'''ἰσχνότερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ἰσχνός''.<br>
'''ἰσχνῶς (adverbe)''' : maigrement.<br>
'''ἴσχω (verbe)''' : tenir.<br>
'''ἴσως (adverbe)''' : également.<br>
'''ἰσαίτατος, -άτη, -ίτατον (adjectif)''' : Superlatif de ''ἴσος''.<br>
'''ἰσαίτερος, -έρα, -ίτερον (adjectif)''' : Comparatif de ''ἴσος''.<br>
'''ἰταλός, -οῦ (nom commun) (m)''' : Bouvillon, taurillon.<br>
'''ἰταμός, -ή, -όν (adjectif)''' : effronté.<br>
'''ἰταμότατα, -, - (adverbe)''' : Superlatif de ''ἰταμῶς''.<br>
'''ἰταμότερον, -, - (adverbe)''' : Comparatif de ''ἰταμῶς''.<br>
'''ἰταμότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ἰταμός''.<br>
'''ἰταμότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ἰταμός''.<br>
'''ἰταμότης, -ητος (nom commun) (f)''' : effronterie.<br>
'''ἰταμῶς (adverbe)''' : effrontément.<br>
'''-ίτης, -ου (suffixe) (m)''' : Suffixe nominal masculin.<br>
'''-ῖτις, -ίτιδος (suffixe) (f)''' : Suffixe nominal féminin.<br>
'''ἴυγξ, -γγος (nom commun) (f)''' : torcol.<br>
'''ἰύζω (verbe)''' : crier de peine ou de joie.<br>
'''ἴφυον, -ου (nom commun) (n)''' : .<br>
'''ἰχθυοέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ἰχθυόεις''.<br>
'''ἰχθυοέστερος, -έρα, -ερον (adjectif)''' : Comparatif de ''ἰχθυόεις''.<br>
'''ἰχθυόεις, -εσσα, -εν (adjectif)''' : poissonneux.<br>
'''ἰχθύς, -ος (nom commun) (m)''' : poisson.<br>
'''ἴχνος, -ους (nom commun) (n)''' : trace de pas.<br>
'''ἰώϐηλος, -ήλου (nom commun) (m)''' : jubilé.<br>
'''ἰῶτα (nom commun) (n)''' : iota.<br>
'''ἰωτακισμός, -οῦ (nom commun) (m)''' : iotacisme.<br>
'''Ἰάκωϐος, -ώϐου (nom propre) (m)''' : Jacob, Jacques.<br>
'''Ἰαπετός, -οῦ (nom propre) (m)''' : Japet.<br>
'''Ἴασος, -άσου (nom propre) (f)''' : Iasos (ville).<br>
'''Ἰάσων, -ονος (nom propre) (m)''' : Jason. (chef des Argonautes)<br>
'''Ἴδας, -α (nom propre) (m)''' : Idas.<br>
'''Ἰδομενεύς, -έως (nom propre) (m)''' : Idoménée.<br>
'''Ἰάμϐλιχος, -ίχου (nom propre) (m)''' : Jamblique.<br>
'''Ἰαω (nom propre) (m)''' : Yahweh.<br>
'''Ἰϐηρία, -ας (nom propre) (m)''' : Ibérie.<br>
'''Ἴϐηρ, -ος (nom commun) (m)''' : Ibère.<br>
'''Ἰγνάτιος, -ίου (nom propre) (m)''' : Ignace.<br>
'''Ἰεριχώ (nom propre) (f)''' : Jéricho.<br>
'''Ἰερειχώ (nom propre) (f)''' : Forme alternative de ''Ἰεριχώ''.<br>
'''Ἱερειχώ (nom propre) (f)''' : Forme alternative de ''Ἰεριχώ''.<br>
'''Ἱεριχοῦς (nom propre) (f)''' : Forme alternative de ''Ἰεριχώ''.<br>
'''Ἱεριχώ, -οῦς (nom propre) (f)''' : Forme alternative de ''Ἰεριχώ''.<br>
'''Ἰέρνη, -ης (nom propre) (f)''' : Irlande.<br>
'''Ἱεροκλῆς, -έους (nom propre) (m)''' : Hiéroclès.<br>
'''Ἱεροσάλημα (nom propre) (f)''' : Forme alternative de ''Ἱερουσαλήμ''.<br>
'''Ἱεροσόλυμα (nom propre) (f/n)''' : Forme alternative de ''Ἱερουσαλήμ''.<br>
'''Ἱερουσαλήμ (nom propre) (f)''' : Jérusalem (ville).<br>
'''Ἰερουσαλήμ (nom propre) (f)''' : Forme alternative de ''Ἱερουσαλήμ''.<br>
'''Ἱερώνυμος, -ύμου (prénom) (m)''' : Jérôme.<br>
'''Ἰεσσαί (nom propre) (m)''' : Jessé.<br>
'''Ἰεσσαῖος, -ίου (nom propre) (m)''' : Forme alternative de ''Ἰεσσαί''.<br>
'''Ἰησαΐας, -ου (nom propre) (m)''' : Forme alternative de ''Ἠσαΐας''.<br>
'''Ἰησοῦς, -οῦ (nom propre) (m)''' : Jésus, Josué.<br>
'''Ἰησαΐας, -ου (nom propre) (m)''' : Forme alternative de ''Ἠσαΐας''.<br>
'''Ἰησοῦς, -οῦ (nom propre) (m)''' : Jésus, Josué.<br>
'''Ἰθάκα, -ης (nom propre) (f)''' : Forme dorienne de ''Ἰθάκη''.<br>
'''Ἰθάκη, -ης (nom propre) (f)''' : Ithaque.<br>
'''Ἴθακος, -άκου (nom commun) (m)''' : Ithacien.<br>
'''Ἰκάριος, -ίου (nom propre) (m)''' : Icarios.<br>
'''Ἴκαρος, -άρου (nom propre) (m)''' : Icare.<br>
'''Ἴκελος, -έλου (nom propre) (m)''' : Icélos.<br>
'''Ἰλιάς, -δος (nom propre) (f)''' : Iliade.<br>
'''Ἴλιον, -ίου (nom propre) (n)''' : Ilion.<br>
'''Ἰλιάς, -δος (nom propre) (f)''' : Iliade.<br>
'''Ἴλιον, -ίου (nom propre) (n)''' : Ilion, Troie.<br>
'''Ἰλλυρία, -ας (nom propre) (f)''' : Illyrie.<br>
'''Ἰλλυρίς, -δος (nom commun) (f)''' : Illyrienne.<br>
'''Ἰλλυριός, -οῦ (nom commun) (m)''' : Illyrien.<br>
'''Ἶλος, Ἴλου (nom propre) (m)''' : Ilos (fils de Tros et de Callirrhoé).<br>
'''Ἵμερος, -έρου (nom propre) (m)''' : Himéros.<br>
'''Ἰμούθης, -ου (nom propre) (m)''' : Imhotep.<br>
'''Ἰξίων, -ονος (nom propre) (m)''' : Ixion.<br>
'''Ἰόλαος, -άου (nom propre) (m)''' : Iolaos.<br>
'''Ἰόλεως, -ώ (nom propre) (m)''' : Forme de ''Ἰόλαος''.<br>
'''Ἰουδαῖα, -ίας (nom propre) (f)''' : Judée.<br>
'''Ἰουδαῖα, -ίας (nom commun) (f)''' : Juive.<br>
'''Ἰουδαῖος, -ίου (nom commun) (m)''' : Juif.<br>
'''Ἰούδας, -α (nom propre) (m)''' : Judas.<br>
'''Ἰουδίθ (nom propre) (f)''' : Judith.<br>
'''Ἵππαρχος, -άρχου (nom commun) (m)''' : Hipparque (Fils cadet de Pisistrate, il fut assassiné par Harmodios et Aristogiton en −514 av. J.-C.).<br>
'''Ἶρις, Ἴριδος (nom propre) (f)''' : [[wikt:Iris|Iris]].<br>
'''Ἰσαάκ (nom propre) (m)''' : Issac.<br>
'''Ἴσακος, - (nom propre) (m)''' : Forme alternative de ''Ἰσαάκ''.<br>
'''Ἴσαυρα, -ας (nom propre) (f)''' : Isaura.<br>
'''Ἰσαυρία, -ας (nom propre) (f)''' : Isaurie.<br>
'''Ἴσαχος, - (nom propre) (m)''' : Forme alternative de ''Ἰσαάκ''.<br>
'''Ἰσίδωρος, -ώρου (nom propre) (m)''' : Isidore.<br>
'''Ἶσις, Ἴσιδος (nom propre) (f)''' : [[wikt:Isis|Isis]].<br>
'''Ἰσθμός, -οῦ (nom propre) (m)''' : Isthmos.<br>
'''Ἱσπανία, -ας (nom propre) (f)''' : Espagne.<br>
'''Ἱσπανίς, -δος (nom commun) (f)''' : Espagnole.<br>
'''Ἱσπανός, -οῦ (nom commun) (m)''' : Espagnol.<br>
'''Ἰσμαήλ (nom propre) (m)''' : Ismaël.<br>
'''Ἰσραήλ (nom propre) (m)''' : Israël.<br>
'''Ἴστρος, -ου (nom propre) (f)''' : Danube inférieur.<br>
'''Ἰταλία -ας (nom propre) (f)''' : Italie.<br>
'''Ἰταλίς, -δος (nom commun) (f)''' : Italienne.<br>
'''Ἰταλιώτης, -ου (nom commun) (m)''' : Italiote.<br>
'''Ἱταλιῶτις, -ώτιδος (nom commun) (f)''' : Italiote.<br>
'''Ἰταλός, -οῦ (nom commun) (m)''' : Italien.<br>
'''Ἴτυς, -ος (nom propre) (m)''' : Itys.<br>
'''Ἴϋγξ, -γγος (nom propre) (f)''' : [[wikt:Jynx|Jynx]].<br>
'''Ἰφθίμη, -ης (nom propre) (f)''' : Iphtimé (sœur de Pénélope).<br>
'''Ἰφιάνασσα, -ας (nom propre) (f)''' : Forme homérique de ''Ἰφιγένεια''.<br>
'''Ἰφιγένεια, -ίας (nom propre) (f)''' : Iphigénie.<br>
'''Ἰφικλῆς, -έους (nom propre) (m)''' : Iphiclès (demi-frère jumeau d'Héraclès).<br>
'''Ἰχθύες, -ων (nom propre) (m)''' : Poissons.<br>
'''Ἰωάννα, -ας (prénom) (f)''' : Jeanne.<br>
'''Ἰωάννης ὁ Χρυσόστομος (nom propre) (m)''' : Jean Chrysostome.<br>
'''Ἰωήλ (nom propre) (m)''' : Joël.<br>
'''Ἰωνάθαν (nom propre) (m)''' : Jonathan.<br>
'''Ἰωνᾶς, -ᾶ (nom propre) (m)''' : Jonas.<br>
'''Ἰωνία, -ας (nom propre) (f)''' : Ionie.<br>
'''Ἴων, -ος (nom propre) (m)''' : Ion (fils de Xouthos).<br>
'''Ἴων, -ος (nom commun) (m)''' : Ionien.<br>
'''Ἰωσήφ (nom propre) (m)''' : Joseph.<br>
'''Ἰωσίας, -ου (nom propre) (m)''' : Josias.<br>
'''Ἰώ, -οῦς (nom propre) (f)''' : [[wikt:Io|Io]].<br>
==Κ==
'''ϗ (symbole)''' : Et. (Abréviation graphique valant ''καί'' en grec comme ''&'' valant ''et'' en français.)<br>
'''καδεμών, -όνος (nom commun) (m)''' : Forme dorienne de ''κηδεμών''.<br>
'''καδιμών, -όνος (nom commun) (m)''' : Forme éolienne de ''κηδεμών''.<br>
'''κάδος, -ου (nom commun) (m)''' : seau.<br>
'''καθαίρω (verbe)''' : Purger, nettoyer ; laver. Étriller.<br>
'''καθαρεύω (verbe)''' : purifier.<br>
'''καθαρίζω (verbe)''' : Nettoyer, purifier. (Médecine) Guérir d’une infection.<br>
'''καθαρός, -ά, -όν (adjectif)''' : pur.<br>
'''καθαρῶς (adverbe)''' : purement.<br>
'''καθαρώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''καθαρός''.<br>
'''καθαρώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''καθαρός''.<br>
'''καθέδρα, ας (nom commun) (f)''' : Ce qui sert à s'asseoir. Action d'être ou de demeurer assis.<br>
'''καθετήρ, -ῆρος (nom commun) (m)''' : Ligne pour pêcher. Collier, pendant ; ornement de femme.<br>
'''καθεύδω (verbe)''' : dormir.<br>
'''καθηγέομαι (verbe)''' : professer.<br>
'''καθηγήτειρα, -ίρας (nom commun) (f)''' : professeure.<br>
'''καθηγητής, -οῦ (nom commun) (m)''' : professeur.<br>
'''καθηγητικός, -ή , -όν (adjectif)''' : professoral.<br>
'''καθηγητικῶς (adverbe)''' : professoralement.<br>
'''καθηγητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''καθηγητικός''.<br>
'''καθηγητικώτερος, -έρη, -ώτερον (adjectif)''' : Comparatif de ''καθηγητικός''.<br>
'''καθηγητικώτατα, -, - (adverbe)''' : Superlatif de ''καθηγητικῶς''.<br>
'''καθηγητικώτερον, -, - (adverbe)''' : Comparatif de ''καθηγητικῶς''.<br>
'''καθήλωσις, -ώσεως (nom commun) (f)''' : détention.<br>
'''καθηλῶ (verbe)''' : détenir.<br>
'''καθίημι (verbe)''' : descendre.<br>
'''κάθημαι (verbe)''' : siéger.<br>
'''καθολικός, -ή, -όν (adjectif)''' : Général ; universel.<br>
'''καθολικῶς (adverbe)''' : Généralement ; universellement.<br>
'''καθολικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''καθολικός''.<br>
'''καθολικώτερος, -έρη, -ώτερον (adjectif)''' : Comparatif de ''καθολικός''.<br>
'''καθολικώτατα, -, - (adverbe)''' : Superlatif de ''καθολικῶς''.<br>
'''καθολικώτερον, -, - (adverbe)''' : Comparatif de ''καθολικῶς''.<br>
'''καθοράω (verbe)''' : regarder.<br>
'''καί (conjonction)''' : Et ; Et même, même. Et en outre. Et ensuite, puis. Ou.<br>
'''καινός, -ή, -όν (adjectif)''' : nouveau.<br>
'''καινότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''καινός''.<br>
'''καινότερος, -έρη, -ότερον (adjectif)''' : Comparatif de ''καινός''.<br>
'''καινότατα, -, - (adverbe)''' : Superlatif de ''καινῶς''.<br>
'''καινότερον, -, - (adverbe)''' : Comparatif de ''καινῶς''.<br>
'''καίνωσις, -ώσεως (nom commun) (f)''' : renouvellement.<br>
'''καινῶς (adverbe)''' : nouvellement.<br>
'''καῖρος, -ίρου (nom commun) (m)''' : embrouille.<br>
'''καιρός, -οῦ (nom commun) (m)''' : temps (durée, époque).<br>
'''καιρόω (verbe)''' : embrouiller.<br>
'''καίρωσις, -ώσεως (nom commun) (f)''' : embrouillage.<br>
'''καίω (verbe)''' : brûler.<br>
'''κάκιον, -, - (adverbe)''' : Comparatif de ''κακῶς''.<br>
'''κάκιστα, -, - (adverbe)''' Superlatif de ''κακῶς''.<br>
'''κακοδαίμων, -ων, -ον (adjectif)''' : malheureux.<br>
'''κακοδαιμόνως (adverbe)''' : malheureusement.<br>
'''κακοδαιμονέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''κακοδαίμων''.<br>
'''κακοδαιμονέστερος, -έρα, -έρερον (adjectif)''' : Comparatif de ''κακοδαίμων''.<br>
'''κακοήθης, -ης, -ες (adjectif)''' : malin.<br>
'''κακοῦργος, -ύργα, -ῦργον (adjectif)''' : Dérangé ; malfaisant.<br>
'''κακός, -ή, όν (adjectif)''' : Mauvais ; laid.<br>
'''κακο- (préfixe)''' : mauvais.<br>
'''κακοφωνία, -ας (nom commun) (f)''' : mauvaise sonorité.<br>
'''κακκῶ (verbe)''' : déféquer ; chier.<br>
'''κακῶς (adverbe)''' : mal.<br>
'''κάλαθος, -άθου (nom commun) (m)''' : corbeille, panier.<br>
'''καλάϊνος, -ΐνη, -άϊνον (adjectif)''' : bleu ciel.<br>
'''καλαΐνως (adverbe)''' : .<br>
'''καλαΐνωτατος, -άτη, -καλαΐνωτατον (adjectif)''' : Superlatif de ''καλάϊνος''.<br>
'''καλαϊνώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''καλάϊνος''.<br>
'''καλαϊνώτατος, -, - (adverbe)''' : Superlatif de ''καλαΐνως''.<br>
'''καλαΐνωτερον, -, - (adverbe)''' : Comparatif de ''καλαΐνως''.<br>
'''κάλιον, -, - (adverbe)''' : Comparatif de ''καλῶς''.<br>
'''κάλιστα, -, - (adverbe)''' Superlatif de ''καλῶς''.<br>
'''καλλι- (préfixe)''' : beau.<br>
'''καλλίπυγοσις, -όσεως (nom commun) (f)''' : .<br>
'''καλλίπυγος, -ος, -ον (adjectif)''' : Qui a de belles fesses.<br>
'''κάλλιστος, -, - (adjectif)''' : Superlatif de ''καλός''.<br>
'''καλλίων, -, - (adjectif)''' : Comparatif de ''καλός''.<br>
'''κάλλος, -ους (nom commun) (n)''' : beauté.<br>
'''καλοήθης, -ης, -ες (adjectif)''' : bénin.<br>
'''καλός, -ή, -όν (adjectif)''' : Beau ; bon.<br>
'''κάλος, -η, -ον (adjectif)''' : Forme éolienne de ''καλός''.<br>
'''καλϝός, -ή, -όν (adjectif)''' : Forme béotienne de ''καλός''.<br>
'''κάλυμμα, -ύμματος (nom commun) (n)''' : couverture.<br>
'''κάλυξ, -κος (nom commun) (m/f)''' : (Botanique) Calice des fleurs ; écale ou peau des fruits.<br>
'''καλύπτω (verbe)''' : Couvrir ; envelopper, cacher.<br>
'''καλῶς (adverbe)''' : Bien ; d’une belle manière.<br>
'''καλῶ (verbe)''' : appeler.<br>
'''καμάρα, -ας (nom commun) (f)''' : Voûte ; chambre voûtée.<br>
'''καμάρη, -ης (nom commun) (f)''' : Forme ionienne de ''καμάρα''.<br>
'''κάμαξ, -κος (nom commun) (m/f)''' : Perche. Hampe de la lance. (Marine) Gouvernail, manche de la rame.<br>
'''κάμηλος, -ήλου (nom commun) (m/f)''' : chameau.<br>
'''καμηλοπάρδαλις, -άλεως (nom commun) (f)''' : girafe.<br>
'''κάμινος, -ίνου (nom commun) (m)''' : cheminée.<br>
'''κάμπη, -ης (nom commun) (f)''' : chenille.<br>
'''καμπή, -ῆς (nom commun) (f)''' : articulation.<br>
'''κάμπος, -ους (nom commun) (n)''' : poisson marin.<br>
'''κάπρος, -ου (nom commun) (n)''' : sanglier.<br>
'''κάμπτω (verbe)''' : courber.<br>
'''καμπύλη, -ης (nom commun) (f)''' : courbure.<br>
'''καμπύλος, -η, -ον (adjectif)''' : Courbé, tordu. Voilé (en parlant d’une roue.)<br>
'''καμπυλότης, -τος (nom commun) (f)''' : courbure.<br>
'''κανδάκη, -ης (nom commun) (f)''' : candace.<br>
'''κανάσσω (verbe)''' : cliquer.<br>
'''καναχή, -ῆς (nom commun) (f)''' : clic.<br>
'''καναχίζω (verbe)''' : claquer.<br>
'''καναχηδά (adverbe)''' : .<br>
'''καναχής, -ής, -ές (adjectif)''' : .<br>
'''καναχῶ (verbe)''' : cliquer.<br>
'''κἄν (conjonction)''' : .<br>
'''κάνθαρος, -άρου (nom commun) (m)''' : cafard.<br>
'''κανονικός, -ή, -όν (adjectif)''' : régulier.<br>
'''κανονικῶς (adverbe)''' : régulièrement.<br>
'''κανονικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κανονικός''.<br>
'''κανονικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κανονικός''.<br>
'''κανονικώτατα, -, - (adverbe)''' : Superlatif de ''κανονικῶς''.<br>
'''κανονικώτερον, -, - (adverbe)''' : Comparatif de ''κανονικῶς''.<br>
'''κανών, -όνος (nom commun) (m)''' : Tige de roseau, règle de maçon ou de charpentier. (Figuré) Règle, modèle ; principe.<br>
'''κάνωπον, -ώπου (nom commun) (n)''' : chou-fleur.<br>
'''καπηλεῖον, -ίου (nom commun) (n)''' : taverne.<br>
'''καπηλεύω (verbe)''' : tenir une taverne.<br>
'''καπηλικός, -ή, -όν (adjectif)''' : tavernier.<br>
'''καπηλίς, -δος (nom commun) (f)''' : tavernière.<br>
'''κάπηλος, -ήλου (nom commun) (m)''' : tavernier.<br>
'''κάππα (nom commun) (n)''' : kappa.<br>
'''κάπτω (verbe)''' : Avaler, prendre (une bouffée d’air).<br>
'''κάρα, -τος (nom commun) (n)''' : Tête ; visage.<br>
'''κάραϐος, -άϐου (nom commun) (m)''' : Crabe ; langouste. Scarabée ; escarbot.<br>
'''κάρδαμον, -ου (nom commun) (n)''' : cresson.<br>
'''καρδία, -ας (nom commun) (f)''' : cœur.<br>
'''καρδιακός, -ή, -ός (adjectif)''' : cardiaque.<br>
'''καρδιακῶς (adverbe)''' : cardiaquement.<br>
'''καρδιακώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''καρδιακός''.<br>
'''καρδιακώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''καρδιακός''.<br>
'''καρδούχιος, -ος, -ον (adjectif)''' : kurde.<br>
'''καρηϐάρεια, -ίας (nom commun) (f)''' : mal de tête.<br>
'''κάρη, -τος (nom commun) (n)''' : Forme homérique et ionienne de ''κάρα''.<br>
'''καρίς, -δος (nom commun) (f)''' : crevette.<br>
'''καρκίνος, -ίνου (nom commun) (m)''' : (Zoologie) Crabe. (Astronomie) Constellation du Cancer. (Médecine) Chancre, tumeur, cancer.
Pince pour saisir ou tenir les objets dans le feu. Compas. Sorte de bandage. (Habillement) Sorte de chaussure.<br>
'''καρπάλιμος, -ος, -ον (adjectif)''' : rapide.<br>
'''καρπός, -οῦ (nom commun) (m)''' : (Sens propre) Fruit, graine ; produit, récolte. (Sens figuré) Produit de quelque chose : enfant (produit du corps), poésie (produit de l'esprit), profit. (Anatomie) Poignet.<br>
'''καρύκιον, -ίου (nom commun) (m)''' : Forme dorienne de ''κηρύκειον''.<br>
'''κᾶρυξ, -άρυκος (nom commun) (m)''' : Forme dorienne de ''κῆρυξ''.<br>
'''καρυόφυλλον, -ύλλου (nom commun) (n)''' : œillet.<br>
'''καρύσσω (verbe)''' : Forme dorienne de ''κηρύσσω''.<br>
'''κάρφος, -ους (nom commun) (n)''' : brindille.<br>
'''κάρχαρος, -α, -ον (adjectif)''' : aiguisé.<br>
'''καρχηδονιακός, -ή, -όν (adjectif)''' : carthaginois.<br>
'''καρῶτον, -ώτου (nom commun) (n)''' : carotte.<br>
'''κάστανον, -άνου (nom commun) (n)''' : châtaigne.<br>
'''κάστον -ου (nom commun) (n)''' : bois.<br>
'''κατά (adverbe ; préposition)''' (Devient ''κατ᾽'' devant un mot commençant par une voyelle à esprit doux, et ''καθ᾽'' devant un mot commençant par une voyelle à esprit rude.) : De haut en bas. En bas. En dessous, au fond. (Par extension) À fond, complètement.
(Avec le génitif) marque l’origine, le point de départ ou le point d’arrivée. Contre, idée d’hostilité. (Avec l’accusatif) suivant, selon.
Par, avec l'idée de succession (un par un). Pendant, avec l’idée de temps. Après, avec l’idée de succession temporelle (jour après jour). À travers, idée de transpercement, de part en part, d’un bout à l’autre.<br>
'''καταϐιϐρώσκω (verbe)''' : .<br>
'''καταγέλαστος, -ος, -ον (adjectif)''' : ridicule.<br>
'''καταγλώττισμα, -ίσματος (nom commun) (n)''' : baiser intrabuccal.<br>
'''καταγράφω (verbe)''' : condamner.<br>
'''καταδικάζω (verbe)''' : condamner.<br>
'''καταδίκη, -ης (nom commun) (f)''' : condamnation.<br>
'''καταδικαστικός, -ή, -όν (adjectif)''' : condamnable.<br>
'''κατάδικος, -ίκου (nom commun) (m/f)''' : condamné.<br>
'''κατάδυσις, -ύσεως (nom commun) (f)''' : émergence.<br>
'''καταδύομαι (verbe)''' : plonger.<br>
'''κατακλύζω (verbe)''' : inonder.<br>
'''κατάκλυσις, -ύσεως (nom commun) (f)''' : inondation.<br>
'''κατακλυσμιαίος, -α, -ον (adjectif)''' : diluvien.<br>
'''κατακλυσμικός, -ή, -όν (adjectif)''' : diluvien.<br>
'''κατακλυσμικότατα, -, - (adverbe)''' : Superlatif de ''κατακλυσμικῶς''.<br>
'''κατακλυσμικότερον, -, - (adverbe)''' : Comparatif de ''κατακλυσμικῶς''.<br>
'''κατακλυσμικῶς (adverbe)''' : désastreusement.<br>
'''κατακλυσμικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κατακλυσμικός''.<br>
'''κατακλυσμικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κατακλυσμικός''.<br>
'''κατακλυσμός, -οῦ (nom commun) (m)''' : déluge.<br>
'''κατακτητής, -οῦ (nom commun) (m)''' : conquérant.<br>
'''κατακτῶμαι (verbe)''' : conquérir.<br>
'''καταλαμϐάνω (verbe)''' : saisir.<br>
'''κατάλαψις, -άψεως (nom commun) (f)''' : Forme dorienne de ''κατάληψις''.<br>
'''κατάληψις, -ήψεως (nom commun) (f)''' : Saisissement. Prise de possession, occupation.<br>
'''κατάλογος, -όγου (nom commun) (m)''' : liste.<br>
'''καταλογογράφησις, -ήσεως (nom commun) (f)''' : listage.<br>
'''καταλογογραφῶ (verbe)''' : lister.<br>
'''κατάπλασμα, -άσματος (nom commun) (n)''' : cataplasme.<br>
'''κατάπτωσις, -ώσεως (nom commun) (f)''' : décadence.<br>
'''κατάπυγον, -ύγου (nom commun) (n)''' : doigt d'honneur.<br>
'''κατάρα, -ας (nom commun) (f)''' : malédiction.<br>
'''καταριέμαι (verbe)''' : maudire.<br>
'''καταστροφικός, -ή, -όν (adjectif)''' : désastreux.<br>
'''καταστροφικότατα, -, - (adverbe)''' : Superlatif de ''καταστροφικῶς''.<br>
'''καταστροφικότερον, -, - (adverbe)''' : Comparatif de ''καταστροφικῶς''.<br>
'''καταστροφικῶς (adverbe)''' : désastreusement.<br>
'''καταστροφικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''καταστροφικός''.<br>
'''καταστροφικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''καταστροφικός''.<br>
'''καταστροφή, -ῆς (nom commun) (f)''' : Désastre, destruction ; renversement.<br>
'''κατάσχεσις, -έσεως (nom commun) (f)''' : confiscation.<br>
'''κατάταξις, -άξεως (nom commun) (f)''' : mise en ordre.<br>
'''κατατάσσω (verbe)''' : mettre en ordre.<br>
'''καταφυγή, -ῆς (nom commun) (f)''' : .<br>
'''καταφύγιον, -ίου (nom commun) (n)''' : abri.<br>
'''κάτεργον, -έργου (nom commun) (n)''' : bagne.<br>
'''κατηγορῶ (verbe)''' : accuser.<br>
'''κατηγορία, -ας (nom commun) (f)''' : Charge ; accusation.<br>
'''κατηγορίη, -ης (nom commun) (f)''' : Forme ionienne de ''κατηγορία''.<br>
'''κατήχησις, -ήσεως (nom commun) (f)''' : .<br>
'''κατηχίζω (verbe)''' : .<br>
'''κατηχισμός, -οῦ (nom commun) (m)''' : .<br>
'''κατηχιστής, -οῦ (nom commun) (m)''' : .<br>
'''κατηχούμενος, -ένου (nom commun) (m)''' : .<br>
'''κατηχῶ (verbe)''' : .<br>
'''κάτοπτρον, -όπτρου (nom commun) (m)''' : miroir.<br>
'''κατώτατος, -άτη, -ώτατον (adjectif)''' : .<br>
'''κατώτερος, -έρα, -ώτερον (adjectif)''' : inférieur.<br>
'''κατωτερότης, -τος (nom commun) (f)''' : infériorité.<br>
'''κάτω (adverbe)''' : sous.<br>
'''καῦκος, -ύκου (nom commun) (m)''' : .<br>
'''καῦµα, -ύματος (nom commun) (n)''' : chaleur de l’été.<br>
'''καύσις, -εως (nom commun) (f)''' : combustion.<br>
'''καυστός, -ή, -όν (adjectif)''' : torride.<br>
'''καυστῶς (adverbe)''' : torridement.<br>
'''καυστώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''καυστός''.<br>
'''καυστώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''καυστός''.<br>
'''καυστώτατα, -, - (adverbe)''' : Superlatif de ''καυστῶς''.<br>
'''καυστώτερον, -, - (adverbe)''' : Comparatif de ''καυστῶς''.<br>
'''καύχημα, -ήματος (nom commun) (n)''' : vantardise.<br>
'''καυχός, -οῦ (nom commun) (m)''' : Forme crétoise de ''χαλκός''.<br>
'''καυχῶμαι (verbe)''' : .<br>
'''κεγχριαῖος, -ία, -ῖον (adjectif)''' : granuleux.<br>
'''κέγχρος, -ου (nom commun) (m)''' : (Botanique) Millet. Grain.<br>
'''κεῖμαι (verbe)''' : être couché, se reposer ; être situé.<br>
'''κείμενον, -ένου (nom commun) (n)''' : texte.<br>
'''κεινός, -ή, -όν (adjectif)''' : Forme ionienne de ''κενός''.<br>
'''κείρω (verbe)''' : .<br>
'''κελαινός, -ή, -όν (adjectif)''' : obscur.<br>
'''κελτικός, -ή, -όν (adjectif)''' : celte.<br>
'''κελτικῶς (adverbe)''' : .<br>
'''κελτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κελτικός''.<br>
'''κελτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κελτικός''.<br>
'''κελτιστί (adverbe)''' : en grec.<br>
'''κενός, -ή, -όν (adjectif)''' : Vide. Vain, frivole.<br>
'''κενϝός, -ή, -όν (adjectif)''' : Forme ancienne de ''κενός''.<br>
'''κενεός, -ή, -όν (adjectif)''' : Forme poétique de ''κενός''.<br>
'''κεντρικός, -ή, -όν (adjectif)''' : central.<br>
'''κέντρον, -ου (nom commun) (n)''' : Aiguillon, dard. Pointe du compas, d’où centre du cercle.<br>
'''κεντῶ (verbe)''' : piquer.<br>
'''κενῶς (adverbe)''' : Habilement, sagement. Ingénieusement, finement.<br>
'''κενώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κενός''.<br>
'''κενώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κενός''.<br>
'''κεραμεύς, -έως (nom commun) (m)''' : potier.<br>
'''κεραμικός, -ή, -όν (adjectif)''' : d’argile.<br>
'''κέραμος, -άμου (nom commun) (m)''' : argile.<br>
'''κεράννυμι (verbe)''' : mélanger.<br>
'''κέρας, -τος (nom commun) (n)''' : Corne. (Par analogie) Bras d’un fleuve ; aile d’une armée ou d’une flotte ; antenne ou vergue d’un navire ; pic d’une montagne. (Sophisme) argument cornu.<br>
'''κεραός, -ά, -όν (adjectif)''' : Cornu ; fait de corne.<br>
'''κεραϝός, -ά, -όν (adjectif)''' : Forme ancienne de ''κεραός''.<br>
'''κερασός, -οῦ (nom commun) (m)''' : cerise.<br>
'''κεραυνός, -οῦ (nom commun) (m)''' : foudre.<br>
'''κέρκηρις, -ήρεως (nom commun) (m)''' : sarcelle.<br>
'''κερκοπίθακος, -άκου (nom commun) (m)''' : Forme dorienne de ''κερκοπίθηκος''.<br>
'''κερκοπίθηκος, -ήκου (nom commun) (m)''' : cercopithèque.<br>
'''κέρκος, -ου (nom commun) (f)''' : queue.<br>
'''κέρκωψ, -πος (nom commun) (m)''' : singe à longue queue.<br>
'''κεστός, -ή, -όν (adjectif)''' : brodé.<br>
'''κεστότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κεστός''.<br>
'''κεστότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κεστός''.<br>
'''κεστῶς (adverbe)''' : .<br>
'''κεφαλαία, -ας (nom commun) (f)''' : mal de tête.<br>
'''κεφαλή, -ῆς (nom commun) (f)''' : tête.<br>
'''κεφάλιον, -ίου (nom commun) (n)''' : petite tête.<br>
'''κεφαλίς, -δος (nom commun) (f)''' : bonnet, chapeau.<br>
'''κηδεμών, -όνος (nom commun) (m)''' : .<br>
'''κηκίς, -ῖδος (nom commun) (f)''' : Jet. Teinture tiré de la galle.<br>
'''κηλίς, -ῖδος (nom commun) (f)''' : .<br>
'''κνημίς, -ῖδος (nom commun) (f)''' : .<br>
'''κρηπίς, -ῖδος (nom commun) (f)''' : .<br>
'''κῆπος, -ήπου (nom commun) (m)''' : jardin ; sorte de singe.<br>
'''κηρός, -οῦ (nom commun) (m)''' : cire.<br>
'''κῆρ, -ος (nom commun) (n)''' : cœur.<br>
'''κῆρυγμα, -ύγματος (nom commun) (n)''' : proclamation à voix haute.<br>
'''κηρύκειον, -ίου (nom commun) (m)''' : caducée.<br>
'''κῆρυξ, -ήρυκος (nom commun) (m)''' : héraut.<br>
'''κηρύσσω (verbe)''' : annoncer.<br>
'''κηρύττω (verbe)''' : Forme attique de ''κηρύσσω''.<br>
'''κηφήν, -ῆνος (nom commun) (m)''' : bourdon.<br>
'''κηφηνώδης, -ης, -ες (adjectif)''' : .<br>
'''κιϐδηλεύω (verbe)''' : falsifier.<br>
'''κιϐδηλία, -ας (nom commun) (f)''' : falsification.<br>
'''κίϐδηλος, -ος, -ου (adjectif)''' : falsifié ; frauduleux.<br>
'''κιϐδηλότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κίϐδηλος''.<br>
'''κιϐδηλότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κίϐδηλος''.<br>
'''κιϐδήλως (adverbe)''' : frauduleusement.<br>
'''κιϐωτός, -οῦ (nom commun) (f)''' : arche.<br>
'''κιγκλίς, -δος (nom commun) (f)''' : bergeronnette.<br>
'''κιδαφεύω (verbe)''' : ruser.<br>
'''κίδαφος, -άφη, -ίδαφον (adjectif)''' : rusé.<br>
'''κίδαφότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κίδαφος''.<br>
'''κίδαφότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κίδαφος''.<br>
'''κιδάφως (adverbe)''' : frauduleusement.<br>
'''κικκάϐη, -ης (nom commun) (f)''' : .<br>
'''κῖκυς, -ίκυος (nom commun) (f)''' : force ; vigueur.<br>
'''κιθάρα, -ας (nom commun) (f)''' : cithare.<br>
'''κιθάρη, -ης (nom commun) (f)''' : Forme ionienne de ''κιθάρα''.<br>
'''κιθών, -ῶνος (nom commun) (m)''' : Forme ionienne de ''χιτών''.<br>
'''κίνδυνος, -ύνου (nom commun) (m)''' : Danger, risque ; entreprise hasardeuse.<br>
'''κινηθμός, -οῦ (nom commun) (m)''' : .<br>
'''κίνημα, -ήματος (nom commun) (n)''' : mouvement (chose).<br>
'''κίνησις, -ήσεως (nom commun) (f)''' : mouvement (processus).<br>
'''κινητέον, -ου (nom commun) (n)''' : .<br>
'''κινητήρ, -ῆρος (nom commun) (m)''' : .<br>
'''κινητής, -οῦ (nom commun) (m)''' : .<br>
'''κινητήριος, -α, -ον (adjectif)''' : .<br>
'''κινητικός, -ή, -όν (adjectif)''' : .<br>
'''κινητός, -ή, -όν (adjectif)''' : .<br>
'''κίνητρον, -ήτρου (nom commun) (n)''' : .<br>
'''κίνναμον, -άμου (nom commun) (n)''' : cannelle.<br>
'''κινῶ (verbe)''' : bouger.<br>
'''κιξάλλης, -ου (nom commun) (m)''' : voleur de grand chemin.<br>
'''κίρκος, -ου (nom commun) (m)''' : (ornithologie) Faucon. Anneau.<br>
'''κιρρός, -ά, -όν (adjectif)''' : brun-roux.<br>
'''κισσαϐίζω (verbe)''' : .<br>
'''κῖσσα, -ης (nom commun) (f)''' : geai des chênes.<br>
'''κίσσα, -ης (nom commun) (f)''' : pie.<br>
'''κισσός, -οῦ (nom commun) (m)''' : lierre.<br>
'''κισσοστεφής, -ής, -ές (adjectif)''' : couronné de lierre.<br>
'''κισσῶ (verbe)''' : .<br>
'''κίττα, -ης (nom commun) (f)''' : Forme attique de ''κίσσα''.<br>
'''κιτών, -ῶνος (nom commun) (m)''' : Forme dorienne de ''χιτών''.<br>
'''κιχόρη, -ης (nom commun) (f)''' : chicorée.<br>
'''κλάδος, -ου (nom commun) (m)''' : Branche (d'arbre), rameau.<br>
'''κλαΐς, -δός (nom commun) (f)''' : Forme dorienne de ''κλείς''.<br>
'''κλᾶϊς, -άϊδος (nom commun) (f)''' : Forme éolienne de ''κλείς''.<br>
'''κλαίω (verbe)''' : Pleurer sur, déplorer. Appeler en criant. Se mettre en pleurs.<br>
'''κλᾶμμα, -άμματος (nom commun) (f)''' : Forme éolienne de ''κλῆμα''.<br>
'''κλαστός, -ή, -όν (adjectif)''' : brisé, cassé.<br>
'''κλαυθμυρίζω (verbe)''' : faire pleurer.<br>
'''κλαῦμα, -ύματος (nom commun) (n)''' : pleur.<br>
'''κλαυσίγελως, -τος (nom commun) (m)''' : .<br>
'''κλαυστός, -ή, -όν (adjectif)''' : Pleuré. Funèbre, triste.<br>
'''κλάω (verbe)''' : Briser, casser.<br>
'''κλεῖθρον, -ίθρου (nom commun) (n)''' : serrure.<br>
'''κλειθροποιός, -οῦ (nom commun) (m)''' : serrurier.<br>
'''κλείς, -δός (nom commun) (f)''' : clé.<br>
'''κλειτορίς, -δος (nom commun) (f)''' : clitoris.<br>
'''κλείω (verbe)''' : fermer.<br>
'''κλέπτης, -ου (nom commun) (m)''' : voleur.<br>
'''κλέπτω (verbe)''' : voler. (dérober)<br>
'''κληΐς, -δός (nom commun) (f)''' : Forme ionienne de ''κλείς''.<br>
'''κλῄς, -δός (nom commun) (f)''' : Forme attique de ''κλείς''.<br>
'''κλέος, -ους (nom commun) (n)''' : gloire.<br>
'''κλεψύδρα, -ας (nom commun) (m)''' : clepsydre.<br>
'''κλέω (verbe)''' : être célèbre.<br>
'''κλῆμα, -ήματος (nom commun) (n)''' : pampre.<br>
'''κληματίζω (verbe)''' : .<br>
'''κληματικός, -ή, -όν (adjectif)''' : .<br>
'''κλημάτινος, -η, ον (adjectif)''' : .<br>
'''κληματίς, -δος (nom commun) (f)''' : Diminutif de ''κλῆμα''.<br>
'''κληματῖτις, -ίτιδος (nom commun) (f)''' : .<br>
'''κληματόδεσις, -εως (nom commun) (f)''' : .<br>
'''κληματοειδής, -ής, -ές (adjectif)''' : .<br>
'''κληματόεις, -εσσα, εν (adjectif)''' : .<br>
'''κληματόομαι (verbe)''' : .<br>
'''κληματώδης, -ης, -ες (adjectif)''' : .<br>
'''κλήρωσις, -ώσεως (nom commun) (f)''' : .<br>
'''κληρονομία, -ας (nom commun) (f)''' : héritage.<br>
'''κληρονομικότης, -τος (nom commun) (f)''' : hérédité.<br>
'''κληρονομικός, -ή, -όν (adjectif)''' : héréditaire.<br>
'''κληρονόμος, -ου (nom commun) (m)''' : héritier.<br>
'''κληρονομῶ (verbe)''' : hériter.<br>
'''κλῆρος, -ήρου (nom commun) (m)''' : .<br>
'''κληρόω (verbe)''' : .<br>
'''κλητήρ, -ῆρος (nom commun) (m)''' : Témoin ; héraut.<br>
'''κλίμα, -τος (nom commun) (n)''' : Inclinaison, pente. Inclinaison de la Terre, latitude, climat.<br>
'''κλῖμαξ, -ίμακος (nom commun) (f)''' : Escalier ; échelle.<br>
'''κλίνη, -ης (nom commun) (f)''' : lit.<br>
'''κλίνω (verbe)''' : Pencher. Pencher sur, s’appuyer sur. Coucher, allonger, étendre.<br>
'''κλίννω (verbe)''' : Forme éolienne de ''κλίνω''.<br>
'''κλίσις, -εως (nom commun) (f)''' : déclinaison.<br>
'''κλίτος, -ους (nom commun) (n)''' : allée.<br>
'''κλῖτος, -ίτους (nom commun) (n)''' : .<br>
'''κλιτύς, -ος (nom commun) (n)''' : Forme béotienne de ''κλῖτος''.<br>
'''κλόνις, -ος (nom commun) (f)''' : sacrum.<br>
'''κλόνος, -ου (nom commun) (m)''' : Confusion ; excitation.<br>
'''κλύδων, -ος (nom commun) (m)''' : Vague. Trouble, agitation, mouvement tumultueux.<br>
'''κλύζω (verbe)''' : Laver, nettoyer. Battre de ses flots, baigner de ses flots.<br>
'''κλυστήρ, -έρος (nom commun) (m)''' : seringue.<br>
'''κλύω (verbe)''' : entendre.<br>
'''κλωβός, -ϐοῦ (nom commun) (m)''' : cage à oiseau.<br>
'''κλών, -ός (nom commun) (m)''' : .<br>
'''κλώψ, -πός (nom commun) (m)''' : voleur.<br>
'''κλῶ (verbe)''' : S’entendre nommer ; entendre parler de soi.<br>
'''κνέφας, -ους (nom commun) (n)''' : Aube ; crépuscule, obscurité.<br>
'''κνῆκος, -ήκου (nom commun) (f)''' : carthame des teinturiers.<br>
'''κνήμη, -ης (nom commun) (f)''' : jambe.<br>
'''κνίδη, -ης (nom commun) (f)''' : ortie.<br>
'''κνίδωσις, -ώσεως (nom commun) (f)''' : urticaire.<br>
'''κνίζω (verbe)''' : Gratter. ; taillader.<br>
'''κοϐαλεία, -ας (nom commun) (f)''' : .<br>
'''κοϐαλεύω (verbe)''' : .<br>
'''κοϐαλίκευμα, -ύματος (nom commun) (n)''' : .<br>
'''κοϐαλισμός, -οῦ (nom commun) (m)''' : .<br>
'''κόϐαλος, -άλου (nom commun) (m)''' : Chenapan ; garnement. Gobelin.<br>
'''κοθαρός, -ά, -όν (adjectif)''' : Forme dorienne de ''καθαρός''.<br>
'''κόθαρος, -ά, -όν (adjectif)''' : Forme éolienne de ''καθαρός''.<br>
'''κοιλάς, -δος (nom commun) (f)''' : vallée.<br>
'''κοῖλος, -ίλη, -ῖλον (adjectif)''' : creux.<br>
'''κοῖλος, -ίλου (nom commun) (m)''' : creux.<br>
'''κοιμάω (verbe)''' : (Actif) Mettre sur une couche, mettre au lit. (Passif) Faire reposer, faire dormir. (Passif, par euphémisme) Faire mourir. (Figuré) Assoupir, apaiser, calmer.<br>
'''κοιμέω (verbe)''' : Forme ionienne de ''κοιμάω''.<br>
'''κοιμητήριον, -ου (nom commun) (n)''' : Dortoir ; cimetière.<br>
'''κοιμίζω (verbe)''' : endormir.<br>
'''κοινοϐουλευτικός, -ή, -όν (adjectif)''' : parlementaire.<br>
'''κοινοϐούλιον, -ίου (nom commun) (n)''' : parlement.<br>
'''κοινός, -ή, -όν (adjectif)''' : commun.<br>
'''κοινότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κοινός''.<br>
'''κοινότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κοινός''.<br>
'''κοινότης, -τος (nom commun) (f)''' : communauté.<br>
'''κοινωνία, -ας (nom commun) (f)''' : société.<br>
'''κοινωνικός, -ή, -όν (adjectif)''' : social.<br>
'''κοινωνικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κοινωνικός''.<br>
'''κοινωνικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κοινωνικός''.<br>
'''κοινωνικῶς (adverbe)''' : communément.<br>
'''κοινωνικώτατος (adverbe)''' : Superlatif de ''κοινωνικῶς''.<br>
'''κοινωνικώτερος (adverbe)''' : Comparatif de ''κοινωνικῶς''.<br>
'''κοινῶ (verbe)''' : Communiquer. Rendre commun à, faire savoir. Mettre en communication, unir. Rendre commun à tous, prostituer, profaner, souiller.
Unir, assembler, ajuster (une pièce d'une construction). (Moyen) Communiquer, mettre en commun. <br>
'''κοῖος, -ίη, -ῖον (adjectif)''' : Forme ionienne de ''ποῖος''.<br>
'''κοῖτος, -ίτου (nom commun) (m)''' : Lit, couche.<br>
'''κόκκος, -ου (nom commun) (m)''' : Graine, grain, pépin. (Entomologie) Cochenille, kermès, galle du chêne kermès. (par analogie) Pilule. (par analogie) Testicules.<br>
'''κόλλαθον, -άθου (nom commun) (n)''' : .<br>
'''κολοιός, -οῦ (nom commun) (m)''' : choucas.<br>
'''κόκκυξ, -υγος (nom commun) (m)''' : coucou, coccyx.<br>
'''κόκκυ (onomatopée)''' : cri du coucou.<br>
'''κωκύω (verbe)''' : crier, se lamenter.<br>
'''κολακεία, -ας (nom commun) (f)''' : flatterie.<br>
'''κολακευτικός, -ή, -όν (adjectif)''' : flatteur.<br>
'''κολακευτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κολακευτικός''.<br>
'''κολακευτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κολακευτικός''.<br>
'''κολακευτικῶς (adverbe)''' : flatteusement.<br>
'''κολακεύω (verbe)''' : flatter.<br>
'''κόλαξ, -κος (nom commun) (m)''' : flatteur.<br>
'''κολάπτω (verbe)''' : frapper.<br>
'''κόλασις, -άσεως (nom commun) (f)''' : punition.<br>
'''κολαφίζω (verbe)''' : gifler.<br>
'''κόλαφος, -άφου (nom commun) (m)''' : gifle.<br>
'''κολεός, -οῦ (nom commun) (m)''' : Fourreau ; vagin.<br>
'''κόλλα, -ης (nom commun) (f)''' : colle.<br>
'''κόλλαϐος, -άϐου (nom commun) (m)''' : Gâteau, petit pain. Cheville qui attache les cordes de la lyre.<br>
'''κολλάω (verbe)''' : coller.<br>
'''κόλλιξ, -κος (nom commun) (m)''' : petit pain.<br>
'''κολλύρα, -ας (nom commun) (f)''' : petit pain.<br>
'''κολλῶ (verbe)''' : .<br>
'''κολοϐός, -ή, -όν (adjectif)''' : écorné, tronqué ; mutilé.<br>
'''κολοιός, -οῦ (nom commun) (m)''' : geai.<br>
'''κόλον, -ου (nom commun) (n)''' : membre, extrémité ; côlon.<br>
'''κολοκύνθη, - (nom commun) (f)''' : .<br>
'''κολοκυνθίς, - (nom commun) (f)''' : .<br>
'''κολοσσός, -οῦ (nom commun) (m)''' : colosse.<br>
'''κολοττός, -οῦ (nom commun) (m)''' : Forme attique de ''κολοσσός''.<br>
'''κολοφών, -ῶνος (nom commun) (m)''' : Sommet. (Figuré) Achèvement, couronnement. Balle de jeu.<br>
'''κόλπος, -ου (nom commun) (m)''' : (Anatomie) Sein (de la mère ou de la nourrice). Utérus. Pli, creux d’un vêtement. Repli, renfoncement de la mer entre deux vagues. Sein de la terre, d’où Enfers. Golfe. Gouffre, cavité ; vallée profonde.<br>
'''κολχικός, -ή, -όν (adjectif)''' : colchien.<br>
'''κολώνη, -ης (nom commun) (f)''' : monticule, tertre ; tumulus.<br>
'''κομϐολόγιον, -ίου (nom commun) (n)''' : bande de nœuds.<br>
'''κόμϐος, -ου (nom commun) (m)''' : nœud.<br>
'''κοµικός, -ή, -όν (adjectif)''' : comique.<br>
'''κοµικότατα, -, - (adverbe)''' : Superlatif de ''κοµικῶς''.<br>
'''κοµικότερον, -, - (adverbe)''' : Comparatif de ''κοµικῶς''.<br>
'''κοµικῶς (adverbe)''' : comiquement.<br>
'''κοµικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κοµικός''.<br>
'''κοµικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κοµικός''.<br>
'''κομπάζω (verbe)''' : hâbler.<br>
'''κομπαστής, -οῦ (nom commun) (m)''' : hâbleur.<br>
'''κομψός, -ή, -όν (adjectif)''' : élégant.<br>
'''κομψότατα, -, - (adverbe)''' : Superlatif de ''κομψῶς''.<br>
'''κομψότερον, -, - (adverbe)''' : Comparatif de ''κομψῶς''.<br>
'''κομψότης, -τος (nom commun) (f)''' : élégance.<br>
'''κομψῶς (adverbe)''' : élégamment.<br>
'''κομψώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κομψός''.<br>
'''κομψώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κομψός''.<br>
'''κονδυλισμίς, -δος (nom commun) (f)''' : chiquenaude ; pichenette.<br>
'''κόνδυ, -ος (nom commun) (n)''' : somm e.<br>
'''κονία, -ας (nom commun) (f)''' : poussière.<br>
'''κονίη, -ης (nom commun) (f)''' : Forme homérique et ionienne de ''κονία''.<br>
'''κόνικλος, -ίκλου (nom commun) (m)''' : lapin.<br>
'''κόνις, -εως (nom commun) (f)''' : cendre ; poussière.<br>
'''κονίς, -δος (nom commun) (f)''' : lente.<br>
'''κοντά (adverbe)''' : près ; à proximité.<br>
'''κοντινός, -ή, -όν (adjectif)''' : proche.<br>
'''κοντινῶς (adverbe)''' : .<br>
'''κοντινώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κοντινός''.<br>
'''κοντινώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κοντινός''.<br>
'''κογχύλιον, -ίου (nom commun) (n)''' : coquillage.<br>
'''κοπριά, -ᾶς (nom commun) (f)''' : fumier.<br>
'''κόπρος, -ου (nom commun) (m)''' : excrément.<br>
'''κόρα, -ας (nom commun) (f)''' : Forme éolienne de ''κόρη''.<br>
'''κόραξ, -κος (nom commun) (m)''' : corbeau.<br>
'''κορέννυμι (verbe)''' : rassasier ; (Au passif et au moyen) Être rassasié. Avoir le dégoût de.<br>
'''κόρευμα, -ύματος (nom commun) (n)''' : virginité.<br>
'''κορεύομαι (verbe)''' : être vierge.<br>
'''κόρη, -ης (nom commun) (f)''' : jeune fille.<br>
'''κορίαννον, -άννου (nom commun) (n)''' : coriandre.<br>
'''κορίζομαι (verbe)''' : caresser.<br>
'''κορμός, -οῦ (nom commun) (m)''' : .<br>
'''κόρος, -ου (nom commun) (m)''' : Jeune garçon ; satiété, dédain.<br>
'''κόρυζα, -ης (nom commun) (f)''' : coryza.<br>
'''κόρυς, -θος (nom commun) (f)''' : Tête ; casque.<br>
'''κορώνη, -ης (nom commun) (f)''' : corneille.<br>
'''κορωνός, -ή, -όν (adjectif)''' : courbe ; recourbé.<br>
'''κόσμημα, -ήματος (nom commun) (n)''' : bijou.<br>
'''κοσμητικός, -ή, -όν (adjectif)''' : Décoratif ; ordonné.<br>
'''κοσμητής, -οῦ (nom commun) (m)''' : Ordonnateur ; arrangeur.<br>
'''κόσμος, -ου (nom commun) (m)''' : Ordre. Monde ; univers.<br>
'''κόσσυκος, -ύκου (nom commun) (m)''' : Forme de ''κόσσυϕος''.<br>
'''κόσσυϕος, -ύϕου (nom commun) (m)''' : merle.<br>
'''κόσος, -η, -ον (adjectif)''' : Forme ionienne de ''πόσος''.<br>
'''κόττος, -ου (m)''' : coq.<br>
'''κόττυϕος, -ύϕου (nom commun) (m)''' : Forme attique de ''κόσσυϕος''.<br>
'''κοτύλη, -ης (nom commun) (f)''' : écuelle.<br>
'''κουρεύς, -έως (nom commun) (m)''' : barbier.<br>
'''κουρεῖον, -ίου (nom commun) (n)''' : boutique du barbier.<br>
'''κούρη, -ης (nom commun) (f)''' : Forme ionienne de ''κόρη''.<br>
'''κοῦρος, -ύρου (nom commun) (m)''' : Forme ionienne de ''κόρος''.<br>
'''κοῦ (adverbe interrogatif)''' : Forme ionienne de ''ποῦ''.<br>
'''κοῦφος, -ύφη, -ῦφον (adjectif)''' : léger.<br>
'''κουφότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κοῦφος''.<br>
'''κουφότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κοῦφος''.<br>
'''κούφως (adverbe)''' : légèrement.<br>
'''κόφινος, -ίνου (nom commun) (m)''' : corbeille.<br>
'''κοχλιάριον, -ίου (nom commun) (n)''' : cuillère.<br>
'''κοχλίας, -ου (nom commun) (m)''' : escargot.<br>
'''κρα (onomatopée)''' : croa.<br>
'''κράϐατος, -άτου (nom commun) (m)''' : Forme macédonienne de ''κράϐϐατος''.<br>
'''κράϐϐατος, -άτου (nom commun) (m)''' : grabat, matelas.<br>
'''κραιπάλη, -ης (nom commun) (f)''' : ivresse.<br>
'''κραίνω (verbe)''' : Commander, gouverner.<br>
'''κράμϐη, -ης (nom commun) (f)''' : chou.<br>
'''κράμϐος, -η, -ον (f)''' : fort (en parlant de la voix ou du son).<br>
'''κρανίον, -ου (nom commun) (n)''' : crâne.<br>
'''κρανοκοπέω (verbe)''' : décapiter.<br>
'''κράνος, -ους (nom commun) (n)''' : casque.<br>
'''κρᾶσις, -άσεως (nom commun) (n)''' : mélange de deux ou plusieurs choses.<br>
'''κράτειρα, -ίρας (nom commun) (f)''' : dirigeante.<br>
'''κράτος, -ους (nom commun) (n)''' : Force du corps, vigueur, solidité. Domination, puissance.<br>
'''κράτωρ, -ορος (nom commun) (m)''' : dirigeant.<br>
'''κραυγάζω (verbe)''' : crier.<br>
'''κραυγή, -ῆς (nom commun) (f)''' : cri.<br>
'''κρέας, -ατος (nom commun) (n)''' : viande.<br>
'''κρεῖας, -ίατος (nom commun) (n)''' : Forme homérique de ''κρέας''.<br>
'''κρείουσα, -ας (nom commun) (f)''' : cri.<br>
'''κρείσσων, -ων, -ον (adjectif)''' : maîtresse ; souveraine.<br>
'''κρείων, -οντος (nom commun) (m)''' : seigneur, maître ; souverain.<br>
'''κρέξ, -κου (nom commun) (m)''' : râle (oiseau).<br>
'''κρέων, -οντος (nom commun) (m)''' : Forme homérique de ''κρείων''.<br>
'''κρημνός, -οῦ (nom commun) (m)''' : falaise, précipice.<br>
'''κρηναῖος, -ία, -ῖον (adjectif)''' : .<br>
'''κρήνη, -ης (nom commun) (m)''' : Source, puits ; fontaine. (Poétique) (Au pluriel) Eau.<br>
'''κρηπίς, -ῖδος (nom commun) (f)''' : pantoufle.<br>
'''κρῆσις, -ήσεως (nom commun) (n)''' : Forme ionienne de ''κρᾶσις''.<br>
'''κρῆς, -ήτος (nom commun) (n)''' : Forme dorienne de ''κρέας''.<br>
'''κρίϐανος, -άνου (nom commun) (m)''' : braséro ; four à pain.<br>
'''κρίκος, -ου (nom commun) (m)''' : anneau.<br>
'''κρῖμα, -ίματος (nom commun) (n)''' : Objet de contestation, contestation, querelle. (Par suite) Jugement, décision judiciaire. Condamnation, peine. (Par extension) Jugement, Décision. Action de juger.<br>
'''κριός, -οῦ (nom commun) (m)''' : bélier.<br>
'''κρίνω (verbe)''' : décider.<br>
'''κρίσις, -ίσεως (nom commun) (f)''' : décision.<br>
'''κριτήριον, -ίου (nom commun) (n)''' : test.<br>
'''κριτήρ, -ῆρος (nom commun) (m)''' : interprète.<br>
'''κριτής, -οῦ (nom commun) (m)''' : juge.<br>
'''κριτικός, -ή, -όν (adjectif)''' : décisif.<br>
'''κριτικῶς (adverbe)''' : décisivement.<br>
'''κριτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κριτικός''.<br>
'''κριτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κριτικός''.<br>
'''κροκόδειλος, -ίλου (nom commun) (m)''' : crocodile.<br>
'''κρόμμυον, -ύου (nom commun) (n)''' : ognon.<br>
'''κρόμυον, -ύου (nom commun) (n)''' : Variante de ''κρόμμυον''.<br>
'''κρόταφος, -άφου (nom commun) (m)''' : tempe.<br>
'''κροῦσις, -ύσεως (nom commun) (f)''' : Choc, collision ; impact, percussion.<br>
'''κρούω (verbe)''' : heurter, choquer. Frapper l’un contre l’autre. Frapper les cordes d’un instrument. Heurter ou pousser pour mettre en mouvement. Heurter avec le doigt pour l’éprouver et le faire résonner. (Figuré) Piquer, chatouiller en parlant de sensations physiques.<br>
'''κρυμός, -οῦ (nom commun) (m)''' : gel.<br>
'''κρύος, -ους (nom commun) (n)''' : froid glacial. Frisson (de crainte).<br>
'''κρύσταλλος, -άλλου (nom commun) (m)''' : Eau congelée ; verre transparent.<br>
'''κροκόδειλος, -ίλου (nom commun) (m)''' : crocodile.<br>
'''κρόκος, -ου (nom commun) (m)''' : safran.<br>
'''κρυπτός, -ός, -όν (adjectif)''' : caché, secret.<br>
'''κρύπτω (verbe)''' : cacher.<br>
'''κρυφᾷ (adverbe)''' : Forme dorienne de ''κρυφῇ''.<br>
'''κρυφῇ (adverbe)''' : secrètement.<br>
'''κρύφος, -ου (nom commun) (f)''' : Action de cacher ; cachette.<br>
'''κρωγμός, -οῦ (nom commun) (m)''' : croassement.<br>
'''κρώζω (verbe)''' : croasser.<br>
'''κρωσσίον, -ου (nom commun) (n)''' : cruche.<br>
'''κρωσσός, -οῦ (nom commun) (m)''' : jarre ; urne.<br>
'''κτείνω (verbe)''' : tuer.<br>
'''κτείς, -ενός (nom commun) (m)''' : Peigne. Râteau. (Au pluriel) Doigts.<br>
'''κτένιον, -ίου (nom commun) (m)''' : Diminutif de ''κτείς''.<br>
'''κτῆνος, -ήνους (nom commun) (n)''' : (Au pluriel) Bétail. (Au singulier) Bête domestique.<br>
'''κτίριον, -ίου (nom commun) (n)''' : bâtiment.<br>
'''κτυπέω (verbe)''' : claquer.<br>
'''κτύπημα, -ήματος (nom commun) (n)''' : claquement.<br>
'''κτῶμαι (verbe)''' : acquérir.<br>
'''κυανός, -ή, -όν (adjectif)''' : bleu.<br>
'''κυϐέρνησις, -ήσεως (nom commun) (f)''' : pilotage ; gouvernement.<br>
'''κυϐερνήτης, -ου (nom commun) (m)''' : pilote ; gouverneur.<br>
'''κυϐερνητικός, -ή, -όν (adjectif)''' : pilotable ; gouvernemental.<br>
'''κυϐερνισμός, -οῦ (nom commun) (m)''' : pilotage ; gouvernement.<br>
'''κυϐερνῶ (verbe)''' : piloter ; gouverner.<br>
'''κῦδος, -ύδους (nom commun) (n)''' : gloire ; renommée.<br>
'''κυῶ (verbe)''' : .<br>
'''κύημα, -ήματος (nom commun) (n)''' : vague.<br>
'''κύησις, -ήσεως (nom commun) (f)''' : grossesse.<br>
'''κύκλος, -ου (nom commun) (m)''' : cercle ; rond.<br>
'''κυκλῶ (verbe)''' : Tourner, rouler. Envelopper, cerner.<br>
'''κύκλωμα, -ώματος (nom commun) (n)''' : circuit.<br>
'''κυκλών, -ῶνος (nom commun) (m)''' : cyclone.<br>
'''κύκνος, -ου (nom commun) (m)''' : cygne.<br>
'''κυλινδρικός, -ή, -όν (adjectif)''' : cylindrique.<br>
'''κύλινδρος, -ίνδρου (nom commun) (m)''' : cylindre.<br>
'''κυλίνδω (verbe)''' : rouler.<br>
'''κύλιξ, -κος (nom commun) (f)''' : coupe (récipient).<br>
'''κυλλός, -ή, -όν (adjectif)''' : Forme chypriote de ''χωλός''.<br>
'''κῦμα, -ύματος (nom commun) (n)''' : onde ; vague.<br>
'''κυναλώπηξ, -εκος (nom commun) (f)''' : .<br>
'''κυνηγός, -οῦ (nom commun) (m)''' : chasseur.<br>
'''κυνηγῶ (verbe)''' : chasser.<br>
'''κύνικλος, -ίκλου (nom commun) (m)''' : Forme de ''κόνικλος''.<br>
'''κυνικός, -ή, -όν (adjectif)''' : canin ; (Philosophie) cynique.<br>
'''κυνόροδον, -όδου (nom commun) (n)''' : églantier des haies.<br>
'''κυπάρισσος, -ίσσου (nom commun) (f)''' : cyprès.<br>
'''κυπάριττος, -ίττου (nom commun) (f)''' : Forme attique de ''κυπάρισσος''.<br>
'''κυπρῖνος, -ίνου (nom commun) (m)''' : carpe.<br>
'''κύπτω (verbe)''' : se baisser en avant. Baisser la tête (ou les yeux) de honte.<br>
'''κυρία, -ας (nom commun) (f)''' : maîtresse ; souveraine.<br>
'''κυριακός, -ή, -όν (adjectif)''' : seigneurial.<br>
'''κύριος, -ίου (nom commun) (m)''' : maître ; souverain.<br>
'''κύρος, -ους (nom commun) (n)''' : prestige.<br>
'''κῦρος, -ύρου (nom commun) (m)''' : décret.<br>
'''κῦρρος, -ύρρου (nom commun) (m)''' : Forme thessalienne de ''κύριος''.<br>
'''κύρωσις, -ώσεως (nom commun) (f)''' : sanction.<br>
'''κυρῶ (verbe)''' : confirmer, ratifier ; sanctionner.<br>
'''κυσός, -οῦ (nom commun) (m)''' : sexe de la femme.<br>
'''κύστιγξ, -γος (nom commun) (f)''' : vésicule.<br>
'''κύστις, -εως (nom commun) (f)''' : sac ; (Au pluriel) Poches sous les yeux.<br>
'''κύσσω (verbe)''' : Forme poétique de ''κύσω''.<br>
'''κύσω (verbe)''' : donner un baiser.<br>
'''κύτος, -ους (nom commun) (n)''' : Creux d'un navire, d'un bouclier, d'une cuirasse. Objet creux. Qui recouvre ou enveloppe.<br>
'''κυφός, -ή, -όν (adjectif)''' : Arqué, bossu. Courbé.<br>
'''κυφότατος, -έρα, -ότερον (adjectif)''' : Comparatif de ''κυφός''.<br>
'''κυφότερος, -άτη, -ότατον (adjectif)''' : Superlatif de ''κυφός''.<br>
'''κύφων, -ονος (nom commun) (m)''' : pilori.<br>
'''κύφωσις, -ώσεως (nom commun) (f)''' : .<br>
'''κυφῶς (adverbe)''' : .<br>
'''κύψελος, -έλου (nom commun) (m)''' : martinet (oiseau).<br>
'''κύων, -νός (nom commun) (m/f)''' : chien ; chienne.<br>
'''κωδωνίζω (verbe)''' : sonner les cloches.<br>
'''κωδώνιον, -ίου (nom commun) (n)''' : clochette.<br>
'''κωδωνόκροτος, - ()''' : .<br>
'''κωδωνοφαλαρόπωλος, - ()''' : .<br>
'''κωδωνοφορέω (verb)''' : .<br>
'''κώδων, -ος (nom commun) (m)''' : cloche.<br>
'''κωκύω (verbe)''' : .<br>
'''κώληψ, -πος (nom commun) (f)''' : jarret.<br>
'''κωλοϐαθριστής, -οῦ (nom commun) (m)''' : Acrobate marchant sur des échasses.<br>
'''κωλόϐαθρον, -άθρου (nom commun) (n)''' : échasse.<br>
'''κῶλον, -ώλου (nom commun) (n)''' : .<br>
'''κώμη, -ης (nom commun) (f)''' : village ; quartier.<br>
'''κωμικός, -ή -όν (adjectif)''' : comique.<br>
'''κωµικότατα, -, - (adverbe)''' : Superlatif de ''κωµικῶς''.<br>
'''κωµικότερον, -, - (adverbe)''' : Comparatif de ''κωµικῶς''.<br>
'''κωµικῶς (adverbe)''' : comiquement.<br>
'''κωµικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''κωµικός''.<br>
'''κωµικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''κωµικός''.<br>
'''κῶμος, -ώμου (nom commun) (m)''' : Festin, banquet. (Par extension) Troupe impétueuse (en parlant des Érinyes), bande.<br>
'''κωμόπολις, -όλεως (nom commun) (n)''' : bourgade.<br>
'''κωμῳδία, -ας (nom commun) (f)''' : poésie satirique.<br>
'''κωνωπεῖον, -ίου (nom commun) (n)''' : moustiquaire.<br>
'''κώνωψ, -ωπος (nom commun) (m)''' : cousin (genre de moustique) ; moucheron.<br>
'''κώπα, -ας (nom commun) (f)''' : Forme dorienne de ''κώπη''.<br>
'''κωπέω (f)''' : ramer.<br>
'''κώπη, -ης (nom commun) (f)''' : Anse, poignée (d’une rame), manche. Rame.<br>
'''κωπίον, -ίου (nom commun) (n)''' : Diminutif de ''κώπη''.<br>
'''κώρα, -ας (nom commun) (f)''' : Forme dorienne de ''κόρη''.<br>
'''κῶρος, -ώρου (nom commun) (m)''' : Forme dorienne de ''κόρος''.<br>
'''κῶς (adverbe)''' : Forme ionienne de ''πῶς''.<br>
'''κώταλις, - (nom commun) (f)''' : cuillère.<br>
'''κωφός, -ή -όν (adjectif)''' : sourd.<br>
'''Καικιλία, -ας (nom propre) (f)''' : Cécile.<br>
'''Καικίλιος, -ίου (nom propre) (m)''' : Cécilius.<br>
'''Κάϊν (nom propre) (m)''' : Caïn.<br>
'''Καινὴ Διαθήκη (locution nominale) (f)''' : Nouveau Testament.<br>
'''Καῖσαρ, -ίσαρος (nom propre) (m)''' : César.<br>
'''Καλλίας, -ου (nom propre) (m)''' : Callias.<br>
'''Καλλικρατίδας, -ου (nom propre) (m)''' : Callicratidas.<br>
'''Καλλίμαχος, -άχου (nom propre) (m)''' : Callimaque.<br>
'''Καλλιόπη, -ης (nom propre) (f)''' : Calliope.<br>
'''Καλλιρόη, -ης (nom propre) (f)''' : Callirhoé.<br>
'''Καλλιστώ, -οῦς (nom propre) (f)''' : Callisto.<br>
'''Καλυψώ, -οῦς (nom propre) (f)''' : Calypso.<br>
'''Καμϐύσης, -ου (nom propre) (m)''' : Cambyse.<br>
'''Κανδάκη, -ης (nom propre) (f)''' : Candace.<br>
'''Καρδοῦχος, -ύχου (nom commun) (m)''' : Kurde.<br>
'''Καρκίνος, -ου (nom propre) (m)''' : Cancer.<br>
'''Κᾶρ, -άρος (nom propre) (f)''' : Forme éolienne de ''Κήρ''.<br>
'''Καρχηδόνιος, -ίου (nom commun) (m)''' : Carthaginois.<br>
'''Καρχηδών, -όνος (nom propre) (f)''' : Carthage.<br>
'''Κασσάνδρα, -ας (nom propre) (f)''' : Cassandre.<br>
'''Κάσσανδρος, -άνδρου (nom commun) (m)''' : Cassandre.<br>
'''Κεϐρίονης, -àου (nom commun) (m)''' : Cébrion.<br>
'''Κελτίς, -δος (nom commun) (f)''' : Celte.<br>
'''Κελτός, -οῦ (nom commun) (m)''' : Celte.<br>
'''Κερασούντιος, -α, -ον (adjectif)''' : cérasien.<br>
'''Κερασοῦς, -ῦντος (nom commun) (m)''' : Cérasus.<br>
'''Κέρϐερος, -έρου (nom propre) (m)''' : Cerbère.<br>
'''Κέρκωψ, -πος (nom propre) (m)''' : Cercops.<br>
'''Κήρ, -ός (nom propre) (f)''' : Une des Kères.<br>
'''Κηφισιά, -ᾶς (nom propre) (f)''' : Céphisia.<br>
'''Κικέρων, -ος (nom propre) (m)''' : Cicéron.<br>
'''Κιλικία, -ας (nom propre) (f)''' : Cilicie.<br>
'''Κίρκη, -ης (nom propre) (f)''' : Circé.<br>
'''Κλεάνθης, -ου (nom propre) (m)''' : Cléante.<br>
'''Κλεισθένης, -ους (nom propre) (m)''' : Clisthène.<br>
'''Κλείτανδρος, -άνδρου (nom propre) (m)''' : Clitandre.<br>
'''Κλειτόμαχος, -άχου (nom propre) (m)''' : Clitomaque.<br>
'''Κλεῖτος, -ίτου (nom propre) (m)''' : Cleithos.<br>
'''Κλειτοφῶν, -τος (nom propre) (m)''' : Clitophon.<br>
'''Κλειώ, -οῦς (nom propre) (f)''' : Clio.<br>
'''Κλεoμήδης, -ους (nom propre) (m)''' : Cléomède.<br>
'''Κλεοπᾶς, -ᾶ (nom propre) (m)''' : Cléopas.<br>
'''Κλεοπάτρα, -ας (nom propre) (f)''' : Cléopâtre.<br>
'''Κλεόπατρος, -άτρου (nom propre) (m)''' : Cléopatros.<br>
'''Κλεώνυμος, -ύμου (nom propre) (m)''' : Cléonyme.<br>
'''Κλωθώ, -οῦς (nom propre) (f)''' : Clotho (première Moire).<br>
'''Κόϊντος, -ΐντου (nom propre) (m)''' : Quentin.<br>
'''Κοῖος, -ίου (nom propre) (m)''' : Céos.<br>
'''Κολοφών, -ῶνος (nom commun) (m)''' : Colophon (ville).<br>
'''Κολχίς, -δος (nom propre) (f)''' : Colchide.<br>
'''Κολχός, -οῦ (nom commun) (m)''' : Colchien.<br>
'''Κόνων, -ος (nom commun) (m)''' : Conon.<br>
'''Κόραμα, -ας (nom propre) (f)''' : Göreme.<br>
'''Κορίνθιος, -ίου (nom commun) (f)''' : Corinthien.<br>
'''Κόρινθος, -ίνθου (nom propre) (f)''' : Corinthe.<br>
'''Κορσίς, -δος (nom propre) (f)''' : Corse.<br>
'''Κόρκυρα, -ύρας (nom propre) (f)''' : Corcyre.<br>
'''Κορυφώ, -οῦς (nom propre) (m)''' : Corfou (île).<br>
'''Κότταλος, -άλου (nom propre) (m)''' : Cottalos.<br>
'''Κουῥάν (nom propre) (m)''' : Coran.<br>
'''Κράτος, -ους (nom propre) (m)''' : Cratos.<br>
'''Κρατύλος, -ου (nom propre) (m)''' : Cratyle.<br>
'''Κρεῖος, -ίου (nom propre) (m)''' : Crios.<br>
'''Κρέουσα, -ας (nom propre) (f)''' : Créuse.<br>
'''Κρέων, -οντος (nom propre) (m)''' : Créon.<br>
'''Κριός, -οῦ (nom propre) (m)''' : Bélier.<br>
'''Κρίτοϐουλος, -ύλου (nom propre) (m)''' : Critobule.<br>
'''Κροῖσος, -ίσου (nom propre) (m)''' : Crésus.<br>
'''Κρίτοϐουλος, -ύλου (nom propre) (m)''' : Critobule.<br>
'''Κροκοδειλόπολις, -όλεως (nom propre) (f)''' : Fayoum.<br>
'''Κτησιφῶν, -τος (nom propre) (m)''' : Ctésiphon.<br>
'''Κτιμένη, -ης (nom propre) (f)''' : Ctimène (sœur d'Ulysse).<br>
'''Κυαξάρης, -ου (nom propre) (m)''' : Cyaxare.<br>
'''Κύθηρα, -ήρων (nom propre) (n)''' : Cythère.<br>
'''Κυθήριος, -ίου (nom commun)''' : Cythérien.<br>
'''Κύκλωψ, -ωπος (nom propre) (m)''' : Cyclope.<br>
'''Κυνόσαργες, -ων (nom propre) (m)''' : Cynosarges.<br>
'''Κύπριος, -ίου (nom commun) (m)''' : Chypriote.<br>
'''Κύπρος, -ου (nom propre) (f)''' : Chypre.<br>
'''Κύριλλος, -ίλλου (nom propre) (m)''' : Cyrille.<br>
'''Κύριος, -ίου (nom propre) (m)''' : Seigneur.<br>
'''Κύρνιος, -ίου (nom commun) (m)''' : Corse.<br>
'''Κύρνος, -ου (nom propre) (f)''' : Corse.<br>
'''Κῦρος, -ύρου (nom propre) (m)''' : Cyrus.<br>
'''Κύψελος, -έλου (nom propre) (m)''' : Cypsélos.<br>
'''Κωκυτός, -τοῦ (nom propre) (m)''' : Cocyte.<br>
'''Κωνσταντῖνος, -ίνου (nom propre) (m)''' : Constantin.<br>
'''Κωνσταντινούπολις, -όλεως (nom propre) (f)''' : Constantinople.<br>
==Λ==
'''λαϐή, -ῆς (nom commun) (f)''' : anse.<br>
'''λάϐρυς, -εως (nom commun) (f)''' : labrys.<br>
'''λαϐύρινθος, -ίνθου (nom commun) (m)''' : labyrinthe.<br>
'''λάγανον, -άνου (nom commun) (n)''' : crêpe.<br>
'''λαγαρός, -ά, -όν (adjectif)''' : Creux, enfoncé ; lâche, ample.<br>
'''λαγνεία, -ας (nom commun) (f)''' : libertinage.<br>
'''λάγνευμα, -ύματος (nom commun) (n)''' : coït (acte).<br>
'''λαγνεύω (verbe)''' : coïter.<br>
'''λάγνης, -ης, -ες (adjectif)''' : Forme attique de ''λάγνος''.<br>
'''λάγνος, -α, -ον (adjectif)''' : libertin, débauché.<br>
'''λαγός, -οῦ (nom commun) (m)''' : lièvre.<br>
'''λάγυνος, -ύνου (nom commun) (m/f)''' : flacon, pichet.<br>
'''λαγών, -όνος (nom commun) (m)''' : flanc.<br>
'''λαγώς, -ώ (nom commun) (m)''' : Forme attique de ''λαγός''.<br>
'''λᾴα -ας (nom commun) (f)''' : Forme dorienne de ''λεία''.<br>
'''λάθος, -ους (nom commun) (n)''' : erreur.<br>
'''λαϊκός, -ή, -όν (adjectif)''' : populaire.<br>
'''λαϊκῶς (adverbe)''' : populairement.<br>
'''λαϊκώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''λαϊκός''.<br>
'''λαϊκώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''λαϊκός''.<br>
'''λαϊκώτατα, -, - (adverbe)''' : Superlatif de ''λαϊκῶς''.<br>
'''λαϊκώτερον, -, - (adverbe)''' : Comparatif de ''λαϊκῶς''.<br>
'''λαιµός, -οῦ (nom commun) (m)''' : cou, gorge.<br>
'''λαῖον, -ίου (nom commun) (n)''' : faux.<br>
'''λαιός, -ά, -όν (adjectif)''' : qui est à gauche.<br>
'''λαιψηρός, -ά, -όν (adjectif)''' : agile, véhément.<br>
'''λαιψηρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''λαιψηρός''.<br>
'''λαιψηρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''λαιψηρός''.<br>
'''λαιψηρῶς (adverbe)''' : agilement, véhémentement.<br>
'''λάκτισμα, -ίσματος (nom commun) (n)''' : coup de pied.<br>
'''λάκυθος, -ύθου (nom commun) (f)''' : Forme dorienne de ''λήκυθος''.<br>
'''λακωνικός, -ή, -όν (adjectif)''' : concis.<br>
'''λακωνικότης, -τος (nom commun) (f)''' : concision.<br>
'''λακωνικῶς (adverbe)''' : concisément.<br>
'''λακωνικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''λακωνικός''.<br>
'''λακωνικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''λακωνικός''.<br>
'''λᾳστής, -οῦ (nom commun) (m)''' : Forme dorienne de ''λῃστής''.<br>
'''λαλέω (verbe)''' : Babiller, bavarder.<br>
'''λαλιά, -ᾶς (nom commun) (f)''' : Babil, bavardage.<br>
'''λάλος, -ος, -ον (adjectif)''' : Babillard, bavard.<br>
'''λαμϐάνω (verbe)''' : prendre.<br>
'''λάμϐδα (nom commun) (n)''' : lambda.<br>
'''λαμπάς, -δος (nom commun) (f)''' : torche.<br>
'''λαμπέτης, -ου (nom commun) (m)''' : .<br>
'''λαμπέτις, -δος (nom commun) (f)''' : .<br>
'''λάμπη, -ης (nom commun) (f)''' : Forme homérique et ionienne de ''λαμπάς''.<br>
'''λαμπηδών, -όνος (nom commun) (f)''' : brillance des yeux.<br>
'''λαμπρός, -ά, -όν (adjectif)''' : brillant.<br>
'''λαμπρῶς (verbe)''' : brillamment.<br>
'''λαμπρώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''λαμπρός''.<br>
'''λαμπρώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''λαμπρός''.<br>
'''λαμπτήρ, -ῆρος (nom commun) (m)''' : briquet, lanterne ; torche.<br>
'''λαμπυρίς, -δος (nom commun) (f)''' : ver luisant.<br>
'''λάμπω (verbe)''' : briller.<br>
'''λάμψις, -εως (nom commun) (f)''' : brillance des étoiles, des éclairs, du Soleil.<br>
'''λανθάνω (verbe)''' : être caché, faire oublier.<br>
'''λαός, -οῦ (nom commun) (m)''' : peuple.<br>
'''λάριξ, -κος (nom commun) (m)''' : mélèze.<br>
'''λάρυγξ, -γος (nom commun) (m)''' : larynx. (Par suite) Gorge, gosier.<br>
'''λάσανον, -άνου (nom commun) (n)''' : Casserole, marmite ; pot.<br>
'''λατρεία, -ας (nom commun) (f)''' : service à gages. Service d’un dieu ; culte, adoration. Soins à donner au corps ou à l'âme.<br>
'''λατρεύς, -έως (nom commun) (m)''' : serviteur engagé.<br>
'''λατρεύω (verbe)''' : Être serviteur à gages. (Généralement) Servir que ce soit en parlant d’homme libre ou d’esclaves. (En particulier) Être serviteur de Dieu.<br>
'''λάτρης, -ου (nom commun) (n)''' : adorateur.<br>
'''λάτρον, -ου (nom commun) (n)''' : Gage, salaire ; rémunération.<br>
'''λάφυρον, -ύρου (nom commun) (n)''' : butin.<br>
'''λέαινα, -ίνης (nom propre) (f)''' : lionne.<br>
'''λέϐης, -τος (nom commun) (m)''' : chaudière.<br>
'''λέγω (verbe)''' : choisir.<br>
'''λεία, -ας (nom commun) (f)''' : pillage.<br>
'''λείϐω (verbe)''' : verser.<br>
'''λειμών, -ῶνος (nom commun) (m)''' : pré.<br>
'''λειμωνήρης, -ης, -ες (adjectif)''' : .<br>
'''λειμωνιάς, -δος (nom commun) (f)''' : lémoniade.<br>
'''λειμώνιον, -ίου (nom commun) (n)''' : .<br>
'''λειμώνιος, -α, -ον (adjectif)''' : .<br>
'''λειμωνίς, -δος (nom commun) (f)''' : lémoniade.<br>
'''λειμωνοειδής, -ής, -ές (adjectif)''' : .<br>
'''λειμωνόθεν (adverbe)''' : .<br>
'''λεῖος, -ία, -ῖον (adjectif)''' : lisse, uni.<br>
'''λείριον, -ίου (nom commun) (n)''' : lis (fleur).<br>
'''λειτούργημα, -ήματος (nom commun) (n)''' : .<br>
'''λειτουργία, -ας (nom commun) (f)''' : Cérémonie publique, service public.<br>
'''λειτουργός, -οῦ (nom commun) (m)''' : Ministre, fonctionnaire. (Religion) Ministre du culte.<br>
'''λειχήν, -ῆνος (nom commun) (m)''' : Lèpre, dartre sur le corps de l’homme. Cal sur la jambe du cheval. Lichen.<br>
'''λείχω (verbe)''' : lécher.<br>
'''λεξικός, -ή, -όν (adjectif)''' : lexical.<br>
'''λεξικόν, -οῦ (nom commun) (n)''' : dictionnaire.<br>
'''λεξικῶς (adverbe)''' : lexicalement.<br>
'''λεξικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''λεξικός''.<br>
'''λεξικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''λεξικός''.<br>
'''λέξις, -εως (nom commun) (f)''' : Parole, action de parler. Élocution, style, manière de parler.<br>
'''λεόντειος, -ος, -ον (adjectif)''' : léonin.<br>
'''λεπίς, -δος (nom commun) (f)''' : Coque d’œuf. Lamelle de métal.<br>
'''λεπτότης, -τος (nom commun) (f)''' : Minceur. Délicatesse.<br>
'''λεπτός, -ή, -όν (adjectif)''' : Dépouillé de sa peau, de sa pellicule. Mince. Délicat.<br>
'''λεπτότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''λεπτός''.<br>
'''λεπτότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''λεπτός''.<br>
'''λεπτῶς (adverbe)''' : Mincement. Délicatement<br>
'''λέπω (verbe)''' : Peler, écosser ; écorcher.<br>
'''λευγαλέος, -α, -ον (adjectif)''' : Triste, malheureux. Irrité, funeste. Désolé.<br>
'''λευκός, -ή, -όν (adjectif)''' : blanc.<br>
'''λεύκη, -ης (nom commun) (f)''' : peuplier.<br>
'''λεύσσω (verbe)''' : voir.<br>
'''λεώϐατος (nom commun)''' : .<br>
'''λεωλογέω (verbe)''' : .<br>
'''λέων, -οντος (nom commun) (m)''' : lion.<br>
'''λεώς, -ώ (nom commun) (m)''' : Forme attique de ''λαός''.<br>
'''λεωσφέτερος''' : .<br>
'''λεωφόρος, -ου (nom commun) (f)''' : Avenue, boulevard.<br>
'''λήθαιος, -α, -ον (adjectif)''' : .<br>
'''ληθάνω (verbe)''' : faire oublier.<br>
'''ληθαργέω (verbe)''' : oublier.<br>
'''ληθαργία, -ας (nom commun) (f)''' : léthargie.<br>
'''ληθαργικός, -ή, -όν (adjectif)''' : léthargique.<br>
'''ληθαργικῶς (adverbe)''' : léthargiquement.<br>
'''ληθαργικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ληθαργικός''.<br>
'''ληθαργικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ληθαργικός''.<br>
'''ληθαργικώτατα, -, - (adverbe)''' : Superlatif de ''ληθαργικῶς''.<br>
'''ληθαργικώτερον, -, - (adverbe)''' : Comparatif de ''ληθαργικῶς''.<br>
'''λήθαργος, -άργου (nom commun) (m)''' : torpeur.<br>
'''λήθη, -ης (nom commun) (f)''' : oubli.<br>
'''ληΐη, -ης (nom commun) (f)''' : Forme ionienne de ''λεία''.<br>
'''ληΐς, - (nom commun) (f)''' : Forme homérique de ''λεία''.<br>
'''ληϊστής, -οῦ (nom commun) (m)''' : Forme ionienne de ''λῃστής''.<br>
'''λήιτον, -ίτου (nom commun) (n)''' : (En Achaïe) mairie.<br>
'''λήκυθος, -ύθου (nom commun) (f)''' : petit vase.<br>
'''λῃστής, -οῦ (nom commun) (m)''' : voleur, brigand. (en particulier) Pirate.<br>
'''λῆξις, -ήξεως (nom commun) (f)''' : tirage au sort.<br>
'''λῆμμα, -ήμματος (nom commun) (n)''' : Bénéfice, salaire, recette. Gain, profit. Bénéfice.<br>
'''λημματιστής, -οῦ (nom commun) (m)''' : receveur.<br>
'''λῆνος, -ήνους (nom commun) (n)''' : laine.<br>
'''ληός, -οῦ (nom commun) (m)''' : Forme ionienne de ''λαός''.<br>
'''λησμοσύνη, -ης (nom commun) (f)''' : oubli.<br>
'''λῆψις, -ήψεως (nom commun) (f)''' : prise.<br>
'''λίϐανος, -άνου (nom commun) (m)''' : oliban.<br>
'''λιϐάς, -δος (nom commun) (f)''' : prairie.<br>
'''λίθος, -ου (nom commun) (m)''' : pierre.<br>
'''λιμήν, -ένος (nom commun) (m)''' : port.<br>
'''λίμνη, -ης (nom commun) (f)''' : lac.<br>
'''λιµός, -οῦ (nom commun) (m)''' : faim.<br>
'''λιποθυμῶ (verbe)''' : s’évanouir.<br>
'''λίπος, -ους (nom commun) (n)''' : graisse ; gras.<br>
'''λιπόψυχος, -ος, -ον (adjectif)''' : pusillanime.<br>
'''λιτότης, -τος (nom commun) (f)''' : simplicité.<br>
'''λιτός, -ή, -όν (adjectif)''' : Uni. Simple, sans apprêts. (Par extension) Pauvre, chétif, faible, petit.<br>
'''λίψ, -ϐος (nom commun) (m)''' : sud-ouest.<br>
'''λογίζομαι (verbe)''' : .<br>
'''λογικός, -ή, -όν (adjectif)''' : intellectuel ; rationnel.<br>
'''λογικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''λογικός''.<br>
'''λογικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''λογικός''.<br>
'''λογικότατα, -, - (adverbe)''' : Superlatif de ''λογικῶς''.<br>
'''λογικότερον, -, - (adverbe)''' : Comparatif de ''λογικῶς''.<br>
'''λογικῶς (adverbe)''' : intellectuellement ; rationnellement.<br>
'''λόγιον, -ίου (nom commun) (n)''' : oracle.<br>
'''λόγος, -ου (nom commun) (m)''' : discours, sujet ; parole.<br>
'''λοιμός, -οῦ (nom commun) (m)''' : peste.<br>
'''λοίμωξις, -ώξεως (nom commun) (f)''' : infestation.<br>
'''λοπαδοτεμαχοσελαχογαλεοκρανιολειψανοδριμυποτριμματοσιλφιοκαραϐομελιτοκατακεχυμενοκιχλεπικοσσυφοφαττοπεριστεραλεκτρυονοπτεκεφαλλιοκιγκλοπελειολαγῳ-οσιραιοϐαφητραγανοπτερυγών (nom commun) (m)''' : bigornocabillofricandortolangoustabricobouillabopoulaupococovin (Plat formé de dix-sept ingrédients différents, décrit par Aristophane dans ''L’Assemblée des femmes''. (v. 1169-1174)<br>
'''λόγχη, -ης (nom commun) (f)''' : lance.<br>
'''λοπάς, -δος (nom commun) (f)''' : écuelle.<br>
'''λορδός, -ή, -όν (adjectif)''' : courbé.<br>
'''λορδότατος, -έρα, -ότερον (adjectif)''' : Comparatif de ''λορδός''.<br>
'''λορδότερος, -άτη, -ότατον (adjectif)''' : Superlatif de ''λορδός''.<br>
'''λορδῶς (adverbe)''' : .<br>
'''λουτήρ, -ῆρος (nom commun) (m)''' : baignoire.<br>
'''λούω (verbe)''' : baigner.<br>
'''λόφος, -ου (nom commun) (m)''' : colline.<br>
'''λοχαγός, -οῦ (nom commun) (m)''' : capitaine.<br>
'''λόχος, -ου (nom commun) (m)''' : compagnie.<br>
'''λύγη, -ης (nom commun) (f)''' : crépuscule, pénombre.<br>
'''λύγξ, -γός (nom commun) (f)''' : hoquet.<br>
'''λύγξ, -κός (nom commun) (m/f)''' : lynx.<br>
'''λυγρός, -ά, -όν (adjectif)''' : Fâcheux, triste. Malfaisant.<br>
'''λύκαινα, -ίνης (nom commun) (f)''' : louve.<br>
'''λυκάνθρωπος, -ώπου (nom commun) (m)''' : loup-garou.<br>
'''λυκίσκος, -ου (nom commun) (m)''' : chien-loup.<br>
'''λύκος, -ου (nom commun) (m)''' : loup.<br>
'''λύμα, -ας (nom commun) (f)''' : Forme dorienne de ''λύμη''.<br>
'''λῦμα, -ύματος (nom commun) (n)''' : .<br>
'''λῦμαξ, -ύμακος (nom commun) (m)''' : .<br>
'''λυμαίνομαι (verbe)''' : .<br>
'''λυμαντήρ, -ῆρος (nom commun) (m)''' : destructeur.<br>
'''λυμαντήριος, -α, -ον (adjectif)''' : injurieux, destructeur.<br>
'''λυμαντής, -οῦ (nom commun) (m)''' : coureur.<br>
'''λυμαντικός, -ή, -όν (adjectif)''' : .<br>
'''λυμάντωρ, - (nom commun) (m)''' : .<br>
'''λῦμαρ, -ύμαρος (nom commun) (m)''' : .<br>
'''λύμασις, -εως (nom commun) (f)''' : .<br>
'''λυμάχη, -ης (nom commun) (f)''' : .<br>
'''λύμη, -ης (nom commun) (f)''' : .<br>
'''λύπη, -ης (nom commun) (f)''' : regret.<br>
'''λυποῦμαι (verbe)''' : être affligé.<br>
'''λυπῶ (verbe)''' : regretter.<br>
'''λύρα, -ας (nom commun) (f)''' : lyre. (Par extension) Constellation de la lyre. (Par extension) Poisson du même nom.<br>
'''λύσσα, -ης (nom commun) (f)''' : Rage. (maladie canine) Fureur belliqueuse, frénésie.<br>
'''λύτρωσις, -ώσεως (nom commun) (f)''' : rédemption.<br>
'''λύττα, -ης (nom commun) (f)''' : Forme attique de ''λύσσα''.<br>
'''λύχνος, -ου (nom commun) (m)''' : lampe.<br>
'''λωρίς, -δος (nom commun) (f)''' : bande.<br>
'''λῶρος, -ώρου (nom commun) (m)''' : bande.<br>
'''Λάζαρος, -άρου (nom propre) (m)''' : Lazare.<br>
'''Λαέρτης, -ου (nom propre) (m)''' : Laërte (père d’Ulysse).<br>
'''Λαῖλαψ, -ίλαπου (nom propre) (m)''' : Lélaps.<br>
'''Λάκαινα, -ίνης (nom commun) (m)''' : Laconienne.<br>
'''Λακεδαιμόνιος, -ίου (nom commun) (m)''' : Lacédémonien.<br>
'''Λακεδαίμων, -ονος (nom propre) (f)''' : Lacédémone.<br>
'''Λακωνία, -ας (nom propre) (m)''' : Laconie.<br>
'''Λάκων, -ος (nom commun) (m)''' : Laconien.<br>
'''Λάμια, -ας (nom propre) (m)''' : Lamia.<br>
'''Λαμπάς, -δος (nom propre) (f)''' : Lampade.<br>
'''Λαμπετίη, -ης (nom propre) (f)''' : Lampétie.<br>
'''Λάμπος, -ου (nom propre) (m)''' : Lampos.<br>
'''Λαοδίκη, -ης (nom propre) (f)''' : Laodicé.<br>
'''Λαοκόων, -οντος (nom propre) (m)''' : Laocoon.<br>
'''Λαομέδων, -οντος (nom propre) (m)''' : Laomédon (fils d’Ilos et d’Eurydice).<br>
'''Λατῖνος, -ίνου (nom propre) (m)''' : Latinos.<br>
'''Λάτιον, -ίου (nom propre) (n)''' : Latium.<br>
'''Λατώ, -οῦς (nom propre) (f)''' : Forme dorienne de ''Λητώ''.<br>
'''Λαύρειον, -ίου (nom propre) (m)''' : Laurion.<br>
'''Λάχεσις, -εως (nom propre) (f)''' : Lachésis (deuxième Moire).<br>
'''Λέανδρος, -άνδρου (nom propre) (m)''' : Léandre.<br>
'''Λεία, -ας (nom propre) (f)''' : Léa.<br>
'''Λεύκιππος, -ίππου (nom propre) (m)''' : Leucippe.<br>
'''Λευκοτεκία, -ας (nom propre) (f)''' : Variante de ''Λoυκoτοκία''.<br>
'''Λεωνίδας, -ου (nom propre) (m)''' : Léonidas.<br>
'''Λέων, -οντος (nom propre) (m)''' : Lion.<br>
'''Λεωχάρης, -ου (nom propre) (m)''' : Léocharès.<br>
'''Λήθη, -ης (nom propre) (f)''' : Léthé.<br>
'''Λητώ, -οῦς (nom propre) (f)''' : Léto.<br>
'''Λιϐύη, -ης (nom propre) (f)''' : Lybie.<br>
'''Λίϐυς, -ος (nom commun) (m)''' : Libyen.<br>
'''Λουκᾶνος, -άνου (nom commun) (m)''' : Lucanien.<br>
'''Λουκᾶς, -ᾶ (nom propre) (m)''' : Luke ; Lucas.<br>
'''Λουκιανός, -οῦ (nom propre) (m)''' : Lucien.<br>
'''Λoυκoτοκία, -ας (nom propre) (f)''' : Lutèce.<br>
'''Λουκρητία, -ας (nom propre) (f)''' : Lucrèce.<br>
'''Λουκρήτιος, -ίου (nom propre) (m)''' : Lucrèce.<br>
'''Λυγκεύς, -έως (nom propre) (m)''' : Lyncée.<br>
'''Λυκάμϐης, -ου (nom propre) (m)''' : Lycambès.<br>
'''Λυκάων, -ονος (nom propre) (m)''' : Lycaon.<br>
'''Λύκειον, -ίου (nom propre) (m)''' : Lycée. (Gymnase au nord-est d’Athènes où enseigna Aristote.)<br>
'''Λυκομήδης, -ου (nom propre) (m)''' : Lycomède.<br>
'''Λυκόπολις, -όλεως (nom propre) (f)''' : Assiout.<br>
'''Λυκοῦργος, -ύργου (nom propre) (m)''' : Lycurgue.<br>
'''Λύσανδρος, -άνδρου (nom propre) (m)''' : Lysandre.<br>
'''Λυσιδίκη, -ης (nom propre) (f)''' : Lysidice.<br>
'''Λύσις, -δος (nom propre) (m)''' : Lysis. (œuvre de Platon)<br>
'''Λῦσις, -ύσεως (nom propre) (m)''' Lysis. (philosophe grec)<br>
'''Λωΐς, -δος (nom propre) (f)''' Loïs. (Personnage du Nouveau Testament : ''Timothée'', 1-5)<br>
==Μ==
'''-μα, -τος (suffixe)''' : suffixe marquant des noms neutres abstraits.<br>
'''μά (particule)''' : par. (Particule interjective.) (+ nom de divinité a l’accusatif)<br>
'''μαγειρεῖον, -ίρου (nom commun) (n)''' : cuisine (pièce).<br>
'''μαγειρεύω (verbe)''' : cuisiner.<br>
'''μαγείρισσα, -ας (nom commun) (f)''' : cuisinière.<br>
'''μάγειρος, -ίρου (nom commun) (m)''' : cuisinier.<br>
'''μαγικός, -ή, -όν (adjectif)''' : magique.<br>
'''μαγικῶς (adverbe)''' : magiquement.<br>
'''μαγικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''μαγικός''.<br>
'''μαγικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''μαγικός''.<br>
'''μάγος, -ου (nom commun) (m)''' : Enchanteur, magicien ; sorcier.<br>
'''μαδαρός, -ά, -όν (adjectif)''' : moite ; mouillé.<br>
'''μαδάω (verbe)''' : être moite/mouillé.<br>
'''μᾶζα, -άζης (nom commun) (f)''' : masse (tas).<br>
'''μαζός, -οῦ (nom commun) (m)''' : sein.<br>
'''μάθημα, -ήματος (nom commun) (n)''' : leçon.<br>
'''μαθητής, -οῦ (nom commun) (m)''' : disciple.<br>
'''μάθησις, -ήσεως (nom commun) (f)''' : apprentissage.<br>
'''µαῖα, -ίας (nom propre) (f)''' : mère ; sage-femme.<br>
'''μαίευμα, -ύµατος (nom commun) (n)''' : accouchement.<br>
'''μαίευσις, -ύσεως (nom propre) (f)''' : accouchement.<br>
'''μαιευτικός, -ή, -όν (adjectif)''' : maïeutique.<br>
'''μαιεύομαι (verbe)''' : faire accoucher.<br>
'''μαίνομαι (verbe)''' : être fou, délirer ; être enragé.<br>
'''μαίομαι (verbe)''' : .<br>
'''μαιμάω (verbe)''' : .<br>
'''μακάριος, -ία, -άριον (adjectif)''' : béni, bienheureux ; heureux, fortuné.<br>
'''μακρός, -ά, -όν (adjectif)''' : (idée d’espace) long. (idée de durée) long. (idée de quantité) Grand, fort.<br>
'''μακρύς, -ιά, -ύ (adjectif)''' : long.<br>
'''μαλακτήρ, -ῆρος (nom commun) (m)''' : .<br>
'''μαλάκτης, -ου (nom commun) (m)''' : masseur.<br>
'''μάλαξις, -άξεως (nom commun) (f)''' : massage.<br>
'''μαλάσσω (verbe)''' : masser.<br>
'''μᾶλον, -άλου (nom commun) (n)''' : Forme dorienne et éolienne de ''μῆλον''. (Au sens de « pomme ».)<br>
'''μαλλός, -οῦ (nom commun) (m)''' : touffe de laine.<br>
'''μάμμη, -ης (nom commun) (f)''' : maman.<br>
'''μανδύα, -ας (nom commun) (f)''' : cape.<br>
'''μανδύη, -ης (nom commun) (f)''' : Forme ionienne de ''μανδύα''.<br>
'''μανθάνω (verbe)''' : Apprendre ; s’apercevoir de. (Par suite) Comprendre.<br>
'''μανία, -ας (nom commun) (f)''' : Folie, démence ; enthousiasme, transport.<br>
'''μανίη, -ης (nom commun) (f)''' : Forme ionienne de ''μανία''.<br>
'''μᾶνις, -άνιος (nom commun) (f)''' : Forme dorienne de ''μῆνις''.<br>
'''μαντεία, -ας (nom commun) (f)''' : Prédiction, oracle ; divination.<br>
'''μαντείη, -ης (nom commun) (f)''' : Forme homérique de ''μαντεία''.<br>
'''μαντηΐη, -ης (nom commun) (f)''' : Forme ionienne de ''μαντεία''.<br>
'''μάντις, -εως (nom commun) (m) ''' : devin.<br>
'''μάραθον, -άθου (nom commun) (n)''' : fenouil.<br>
'''μαργαρίδης, -ου (nom commun) (m) ''' : Forme ionienne de ''μαργαρίτης''.<br>
'''μαργαρίτα, -ας (nom commun) (f) ''' : marguerite.<br>
'''μαργαρίτης, -ου (nom commun) (m) ''' : perle.<br>
'''μαρμαίρω (verbe)''' : briller.<br>
'''μαρμάρεος, -έα, -άρεον (adjectif) ''' : brillant ; fait de marbre.<br>
'''μαρμάρινος, -η, -ον (adjectif) ''' : marmoréen.<br>
'''μάρμαρος, -άρου (nom commun) (m)''' : marbre.<br>
'''μαρμαρώδης, -ης, -ες (adjectif)''' : marbré.<br>
'''μάρσιπος, -ίπου (nom commun) (m)''' : sac.<br>
'''μαρσίππιον, -ίου (nom commun) (n)''' : petit sac.<br>
'''μαρτυρέω (verbe)''' : .<br>
'''μαρτύρημα, -ήματος (nom commun) (n)''' : témoignage.<br>
'''μαρτυρία, -ας (nom commun) (f)''' : témoignage.<br>
'''μαρτύριον, -ίου (nom commun) (n)''' : Témoignage, preuve. Sanctuaire dédié à un martyr.<br>
'''μάρτυρ, -ος (nom commun) (m/f)''' : Forme éolienne de ''μάρτυς''.<br>
'''μάρτυς, -ρος (nom commun) (m/f)''' : témoin ; témoin de Dieu.<br>
'''μασδός, -οῦ (nom commun) (m)''' : Forme dorienne de ''μαζός''.<br>
'''μασθός, -οῦ (nom commun) (m)''' : Forme tardive de ''μαζός''.<br>
'''μάσσω (verbe)''' : pétrir, masser.<br>
'''μάσταξ, -κος (nom commun) (f)''' : mâchoire.<br>
'''μαστιάω (verbe)''' : Forme homérique de ''μαστίζω''.<br>
'''μαστίγωσις, -ώσεως (nom commun) (f)''' : flagellation.<br>
'''μαστιγῶ (verbe)''' : fouetter.<br>
'''μαστίζω (verbe)''' : flageller.<br>
'''μαστίσδω (verbe)''' : Forme dorienne de ''μαστίζω''.<br>
'''μάστιξ, -ίγος (nom commun) (f)''' : fouet.<br>
'''μαστός, -οῦ (nom commun) (m)''' : mamelle ; sein.<br>
'''μασχάλη, -ης (nom commun) (f)''' : aisselle (cavité placée sous le bras).<br>
'''μάταιος, -ία, -αιον (adjectif)''' : futile, vain ; frivole.<br>
'''ματαιότης, -τος (nom commun) (f)''' : futilité, vanité ; frivolité.<br>
'''μάτημι (verbe)''' : Forme éolienne de ''πατέω''.<br>
'''μάτηρ, -ρός (nom commun) (f)''' : Forme dorienne de ''μήτηρ''.<br>
'''μάττω (verbe)''' : Forme attique de ''μάσσω''.<br>
'''μαυρός, -ός, -όν (adjectif)''' : Forme alternative de ''ἀμαυρός''.<br>
'''μάχαιρα, -ίρας (nom commun) (f)''' : sabre.<br>
'''μαχαιτάς, -δος (nom commun) (m)''' : Forme éolienne de ''μαχητής''.<br>
'''μαχανά, -ᾶς (nom commun) (f)''' : Forme dorienne de ''μηχανή''.<br>
'''μαχατάρ, -ος (nom commun) (m)''' : Forme laconienne de ''μαχητής''.<br>
'''μαχάτας, -ου (nom commun) (m)''' : Forme dorienne de ''μαχητής''.<br>
'''μαχέομαι (verbe)''' : Forme ionienne de ''μάχομαι''.<br>
'''μάχη, -ῆς (nom commun) (f)''' : Combat ; bataille.<br>
'''μαχητής, -οῦ (nom commun) (m)''' : combattant.<br>
'''μάχλος, -ος, -ον (adjectif)''' : libertin, débauché.<br>
'''μάχομαι (verbe)''' : combattre.<br>
'''μᾶχος, -άχου (nom commun) (n)''' : Forme dorienne de ''μῆχος''.<br>
'''μέγαθος, -άθεος (nom commun) (n)''' : Forme ionienne de ''μέγεθος''.<br>
'''μεγαλομανής, -ής, -ές (adjectif)''' : mégalomane.<br>
'''μέγας, -άλη, -α (adjectif)''' : grand.<br>
'''μέγεθος, -έθους (nom commun) (n)''' : Grandeur, hauteur.<br>
'''μεγιστάν, -ᾶνος (nom commun) (m)''' : magnat.<br>
'''μέγιστος, -η, -ον (adjectif)''' : Superlatif de ''μέγας''.<br>
'''μέδος, -ου (nom commun) (m)''' : hydromel.<br>
'''μέδων, -οντος (nom commun) (m)''' : seigneur.<br>
'''μέδω (verbe)''' : Commander, régner.<br>
'''μεθίημι (verbe)''' : .<br>
'''μέθυστος, -ος, -ον (adjectif)''' : ivre.<br>
'''μέθυ, -ος (nom commun) (n)''' : vin.<br>
'''μεθύω (verbe)''' : être ivre.<br>
'''μεῖγμα, -ίγματος (nom commun) (n)''' : mixture.<br>
'''μείζων, -ων, -ον (adjectif)''' : Comparatif de ''μέγας''.<br>
'''μεῖξις, -ίξεως (nom commun) (f)''' : .<br>
'''μεῖλον, -ίλου (nom commun) (n)''' : Forme béotienne de ''μῆλον''. (Au sens de « petit bétail ».)<br>
'''μεῖραξ, -ίρακος (nom commun) (m/f)''' : jeune fille ; jeune garçon.<br>
'''μείρομαι (verbe)''' : attribuer ; prendre.<br>
'''μείωσις, -ώσεως (nom commun) (f)''' : réduction.<br>
'''μειῶ (verbe)''' : réduire.<br>
'''μελάμπυγος, -ος, -ον (adjectif)''' : Qui a les fesses noires.<br>
'''μελάντατος, -άτη, -άντατον (adjectif)''' : Superlatif de ''μέλας''.<br>
'''μελάντερος, -έρα, -άντερον (adjectif)''' : Comparatif de ''μέλας''.<br>
'''μελάνως (adverbe)''' : en noir.<br>
'''μέλας, -αινα, -αν (adjectif)''' : noir ; sombre.<br>
'''μελεαγρίς, -δος (nom commun) (f)''' : pintade.<br>
'''μελετηρός, -ή, -όν (adjectif)''' : studieux.<br>
'''μελετῶ (verbe)''' : étudier.<br>
'''μελία, -ας (nom commun) (f)''' : frêne.<br>
'''μελίζω (verbe)''' : moduler, chanter.<br>
'''μελίη, -ης (nom commun) (f)''' : Forme ionienne de ''μελία''.<br>
'''μέλι, -τος (nom commun) (f)''' : miel.<br>
'''μέλισσα, -ης (nom commun) (f)''' : abeille.<br>
'''μέλος, -ους (nom commun) (n)''' : Membre, articulation. Chant. <br>
'''μελῳδία, -ας (nom commun) (f)''' : mélodie.<br>
'''μελῳδικός, -ή, -όν (adjectif)''' : mélodieux.<br>
'''μελῳδός, -ή, -όν (adjectif)''' : musical.<br>
'''μέλω (verbe)''' : être un objet de soin ; prendre soin de ; (impersonnel) être un sujet de souci.<br>
'''μέλλων, -οντος (nom commun) (m)''' : futur.<br>
'''μέμψις, -εως (nom commun) (f)''' : blâme ; censure.<br>
'''μέμφομαι (verbe)''' : reprocher.<br>
'''μέν (particule)''' : .<br>
'''μένος, -ους (nom commun) (n)''' : Esprit. Courage, cœur, ardeur, volonté. Désir. Force, violence.<br>
'''μέντοι (particule)''' : cependant.<br>
'''μένω (verbe)''' : rester, demeurer. Attendre<br>
'''μερίς, -δος (nom commun) (f)''' : part.<br>
'''μέρος, -ους (nom commun) (n)''' : partie.<br>
'''μεσαιπόλιος -ος, -ον (adjectif)''' : .<br>
'''µεσοστιχίς, -δος (nom commun) (f)''' : mésostiche.<br>
'''μέσος, -η, -ον (adjectif)''' : médian.<br>
'''μέσος, -ου (nom commun) (m)''' : majeur.<br>
'''μεστός, -ή, -όν (adjectif)''' : plein.<br>
'''μεστῶς (adverbe)''' : pleinement.<br>
'''μεστώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''μεστός''.<br>
'''μεστώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''μεστός''.<br>
'''μεστῶ (verbe)''' : être plein.<br>
'''μέσως (adverbe)''' : au milieu de.<br>
'''μεσώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''μέσος''.<br>
'''μεσώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''μέσος''.<br>
'''μεσηγύ (adverbe)''' : au milieu.<br>
'''μετά (adverbe)''' (Devient ''μετ’'' devant un mot commençant par une voyelle à esprit doux, et ''μεθ’'' devant un mot commençant par une voyelle à esprit rude.) : Au milieu, parmi.<br>
'''μετα- (préfixe)''' : .<br>
'''μεταϐαίνω (verbe)''' : changer ; dépasser.<br>
'''μετάϐασις, -άσεως (nom commun) (f)''' : changement ; dépassement.<br>
'''μεταϐίϐασις, -άσεως (nom commun) (f)''' : transfert ; transmission.<br>
'''μεταϐιϐάζω (verbe)''' : transférer ; transmettre.<br>
'''μεταγιγνώσκω (verbe)''' : regretter.<br>
'''μετακινῶ (verbe)''' : .<br>
'''μεταμέλεια, -ας (nom commun) (f)''' : repentir.<br>
'''μεταμέλω (verbe)''' : se repentir.<br>
'''μεταμόρφωσις, -ώσεως (nom commun) (f)''' : transformation.<br>
'''μετάνοια, -ίας (nom commun) (f)''' : repentance ; pénitence.<br>
'''μετανοῶ (verbe)''' : faire pénitence.<br>
'''μέταξα, -άξης (nom commun) (f)''' : soie.<br>
'''µεταξύ (préposition)''' : entre.<br>
'''μεταφέρω (verbe)''' : transporter.<br>
'''μεταφορά, -άς (nom commun) (f)''' : transport.<br>
'''μεταφράζω (verbe)''' : traduire.<br>
'''μετάφρασις, -άσεως (nom commun) (f)''' : traduction.<br>
'''μετέωρος, -ος, -ον (adjectif)''' : élevé.<br>
'''μέτοικος, -ίκου (nom commun) (m/f)''' : Individu étranger à une cité.<br>
'''μετεωρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''μετέωρος''.<br>
'''μετεωρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''μετέωρος''.<br>
'''μετεωρότατα (adverbe)''' : Superlatif de ''μετεώρως''.<br>
'''μετεωρότερον (adverbe)''' : Comparatif de ''μετεώρως''.<br>
'''μετεώρως (adverbe)''' : de façon élevée.<br>
'''μέτρον, -ου (nom commun) (n)''' : mesure.<br>
'''μέτωπον, -ώπου (nom commun) (n)''' : front.<br>
'''μέχρι (conjonction)''' : jusqu'à ce que.<br>
'''μηδέν (adjectif numéral)''' : zéro.<br>
'''μηδέποτε (adverbe)''' : jamais.<br>
'''μηδικός, -ή, -όν (adjectif)''' : mède.<br>
'''μῆδος, -ήδους (nom commun) (n)''' : mesure.<br>
'''μή (particule)''' : non (subjectif).<br>
'''μηλίτης, -ου (nom commun) (m)''' : cidre.<br>
'''μῆλον, -ήλου (nom commun) (n)''' : Petit bétail ; pomme.<br>
'''μηνιαῖος, -ία -ῖον (suffixe)''' : mensuel.<br>
'''μηναιότατος, -άτη, -ότατον (suffixe)''' : Forme superlative de ''μηνιαῖος''.<br>
'''μηναιότερος, -έρα, -ότερον (suffixe)''' : Forme comapartive de ''μηνιαῖος''.<br>
'''μηναίως (suffixe)''' : mensuellement.<br>
'''μήνη, -ης (nom commun) (f)''' : mère.<br>
'''μῆνιγξ, -ήνιγγος (nom commun) (f)''' : membrane.<br>
'''μῆνις, -ήνιος (nom commun) (f)''' : colère.<br>
'''μήν, -ός (nom commun) (m)''' : mois.<br>
'''μηριαῖος, -ία, -ῖον (adjectif)''' : fémural.<br>
'''μηρóς, -οῦ (nom commun) (m)''' : côté, flanc ; cuisse, fémur.<br>
'''μήτηρ, -ρός (nom commun) (f)''' : mère.<br>
'''μητιάω (verbe)''' : ruse.<br>
'''μητιόεις, -εσσα, -εν (adjectif)''' : .<br>
'''μῆτις, -ήτεως (nom commun) (f)''' : ruse.<br>
'''μήτις, -ις, -ι (pronom)''' : personne.<br>
'''μήτρα, -ας (nom commun) (f)''' : matrice ; ventre ou sein de la mère.<br>
'''μήτρη, -ης (nom commun) (f)''' : Forme ionienne de ''μήτρα''.<br>
'''μητροκτονία, -ας (nom commun) (m)''' : matricide.<br>
'''μητροκτόνος, -ου (nom commun) (m)''' : matricide.<br>
'''μητρυιά, -ᾶς (nom commun) (f)''' : belle-mère.<br>
'''μητρυιός, -οῦ (nom commun) (m)''' : beau-père.<br>
'''μηχανή, -ῆς (nom commun) (f)''' : machine ; engin. Invention.<br>
'''μηχανικός, -ή, -όν (f)''' : mécanique ; ingénieux.<br>
'''μηχανικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''μηχανικός''.<br>
'''μηχανικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''μηχανικός''.<br>
'''μηχανικῶς (adverbe)''' : mécaniquement ; ingénieusement.<br>
'''μῆχος, -ήχους (nom commun) (n)''' : .<br>
'''μιαίνω (verbe)''' : Salir, souiller, polluer, profaner, teindre, colorer.<br>
'''μιαρός, -ά, -όν (adjectif)''' : impur.<br>
'''μίασμα, -άσματος (nom commun) (n)''' : Souillure provenant d’un meurtre. Personne souillée d’un meurtre.<br>
'''μιασμός, -οῦ (nom commun) (m)''' : Action de salir, souiller, tacher ; deuil, vêtements salis en signe de deuil, teinture.<br>
'''μίγδην (adverbe)''' : confusément.<br>
'''μῖγμα, -ίγματος (nom commun) (n)''' : .<br>
'''μικρός, -ά, -όν (adjectif)''' : petit.<br>
'''μικρός, -οῦ (nom commun) (m)''' : auriculaire.<br>
'''μικρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''μικρός''.<br>
'''μικρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''μικρός''.<br>
'''μικρῶς (adverbe)''' : .<br>
'''μιμάς, -δος (nom commun) (f)''' : actrice.<br>
'''μιμεῖσθαι (verbe)''' : imiter.<br>
'''μίμημα, -ήματος (nom commun) (n)''' : Imitation, copie ; contrefaçon.<br>
'''μίμησις, -ήσεως (nom commun) (f)''' : Imitation, représentation.<br>
'''μιμητικός, -ή, -όν (adjectif)''' : représentatif.<br>
'''μιμητικῶς (adverbe)''' : représentativement.<br>
'''μιμητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''μιμητικός''.<br>
'''μιμητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''μιμητικός''.<br>
'''μιμητικώτατα, -, - (adverbe)''' : Superlatif de ''μιμητικῶς''.<br>
'''μιμητικώτερον, -, - (adverbe)''' : Comparatif de ''μιμητικῶς''.<br>
'''μιμναίσκω (verbe)''' : Forme éolienne de ''μιμνήσκω''.<br>
'''μιμνήσκω (verbe)''' : Rappeler à la mémoire. Rappeler une chose, mentionner.<br>
'''μῖμος, -ίμου (nom commun) (m)''' : acteur.<br>
'''μιμοῦμαι (verbe)''' : mimer.<br>
'''μίνθη, -ης (nom commun) (f)''' : menthe.<br>
'''µινώταυρος, -ύρου (nom commun) (m)''' : minotaure.<br>
'''μισανδρία, -ας (nom commun) (f)''' : misandrie.<br>
'''μισάνδρος, -ος, -ον (adjectif)''' : misandre.<br>
'''μισανθρωπία, -ας (nom commun) (f)''' : misanthropie.<br>
'''μισάνθρωπος, -ος, -ον (adjectif)''' : misanthropie.<br>
'''μισθός, -οῦ (nom commun) (m)''' : salaire.<br>
'''μισθοφόρος, -ου (nom commun) (m)''' : mercenaire.<br>
'''μισογύνης, -ου (nom commun) (m)''' : matcho.<br>
'''μισογυνία, -ας (nom commun) (f)''' : misogynie.<br>
'''μῖσος, -ίσους (nom commun) (n)''' : Haine, aversion ; objet de haine.<br>
'''μισῶ (verbe)''' : détester, haïr.<br>
'''μίτος, -ου (nom commun) (m)''' : fil.<br>
'''μίτρα, -ας (nom commun) (f)''' : Ceinture, bandeau ; chapelet de victoire. Guirlande.<br>
'''μίτρη, -ης (nom commun) (f)''' : Forme ionienne de ''μίτρα''.<br>
'''μίτωσις, -ώσεως (nom commun) (f)''' : mitose.<br>
'''μνᾶ, -ᾶς (nom commun) (f)''' : mine (monnaie).<br>
'''μνᾶμα, -άματος (nom commun) (n)''' : Forme dorienne de ''μνῆμα''.<br>
'''μνάμων, -ων, -ον (adjectif)''' : Forme dorienne de ''μνήμων''.<br>
'''μνάομαι (verbe)''' : .<br>
'''μναστήρ, -ῆρος (nom commun) (m)''' : Forme dorienne de ''μνηστήρ''.<br>
'''μνᾶστις, -άστεως (nom commun) (f)''' : Forme dorienne de ''μνῆστις''.<br>
'''μνῆμα, -ήματος (nom commun) (n)''' : tombe.<br>
'''μνημεῖον, -ίου (nom commun) (n)''' : monument.<br>
'''μνημήιον, -ίου (nom commun) (n)''' : Forme dorienne de ''μνημεῖον''.<br>
'''μνήμων, -ων, -ον (adjectif)''' : .<br>
'''μνηστήρ, -ῆρος (nom commun) (m)''' : prétendant.<br>
'''μνῆστις, -ήστεως (nom commun) (f)''' : .<br>
'''µvοῦς, -οῦ (nom commun) (m)''' : chatte (sexe féminin).<br>
'''μόγις (adverbe)'''' : à peine.<br>
'''μόγος, -ου (nom commun) (m)''' : peine ; labeur.<br>
'''μόλις (adverbe)''' : Variante de ''μόγις''.<br>
'''µοῖρα, -ίρας (nom commun) (f)''' : part ; portion. Parti politique.<br>
'''μοιχαλίς, -δος (nom commun) (f)''' : adultère (celle qui la commet).<br>
'''μοιχεία, -ας (nom commun) (f)''' : adultère.<br>
'''μοιχεύω (verbe)''' : commettre l’adultère.<br>
'''μοιχός, -οῦ (nom commun) (m)''' : adultère (celui qui la commet).<br>
'''μόλυϐδος, -ύϐδου (nom commun) (m)''' : plomb.<br>
'''μόλυνσις, -ύνσεως (nom commun) (f)''' : infection.<br>
'''μομφή, -ῆς (nom commun) (f)''' : reproche.<br>
'''μονάς, -δος (nom commun) (f)''' : unité.<br>
'''μοναχεῖον, -ίου (nom commun) (n)''' : monastère.<br>
'''μοναχός, -οῦ (nom commun) (m)''' : moine.<br>
'''μονή, -ῆς (nom commun) (f)''' : action de s’arrêter ; lieu où l’on séjourne.<br>
'''μονόκερως, -έρωτος (nom commun) (f)''' : licorne.<br>
'''μονομαχεῖον, -ίου (nom commun) (n)''' : lieu de duel.<br>
'''μονομαχέω (verbe)''' : se battre en duel.<br>
'''μονομαχία, -ας (nom commun) (f)''' : duel.<br>
'''μονομάχος, -ου (nom commun) (m)''' : duelliste.<br>
'''μονόφθογγος, -όγγου (nom commun) (f)''' : monophtongue.<br>
'''μόνος, -η, -ον (adjectif)''' : Seul, unique ; solitaire.<br>
'''μόνϝος, -η, -ον (adjectif)''' : Forme ancienne de ''μόνος''.<br>
'''μονῳδία, -ας (nom commun) (f)''' : .<br>
'''μόνως (adverbe)''' : uniquement.<br>
'''μονώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''μόνος''.<br>
'''μονώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''μόνος''.<br>
'''μορμολύκα, -ας (nom commun) (f)''' : Forme dorienne de ''μορμολύκη''.<br>
'''μορμολυκεῖον, -ίου (nom commun) (n)''' : croque-mitaine.<br>
'''μορμολύκη, -ης (nom commun) (f)''' : .<br>
'''μορμωτός, -ή, -όν (adjectif)''' : effroyable.<br>
'''μορμῶ -οῦς (nom commun) (m)''' : monstre marin.<br>
'''μορφή, -ῆς (nom commun) (f)''' : forme.<br>
'''μόρφωσις, -ώσεως (nom commun) (f)''' : formation.<br>
'''μορφῶ (verbe)''' : former.<br>
'''μόσχευμα, -ύματος (nom commun) (n)''' : plant.<br>
'''μοσχεύω (verbe)''' : planter.<br>
'''μοσχοκαρύδιον, -ίου (nom commun) (n)''' : Diminutif de ''μοσχοκάρυον''.<br>
'''μοσχοκάρυον, -ύου (nom commun) (n)''' : noix de muscade.<br>
'''μόσχος, -ου (nom commun) (m)''' : Musc ; pousse.<br>
'''μοχθηρός, -ά, -όν (adjectif)''' : malveillant ; souffrant.<br>
'''μοχθηρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''μοχθηρός''.<br>
'''μοχθηρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''μοχθηρός''.<br>
'''μοχθηρῶς, -ά, -όν (adverbe)''' : .<br>
'''μόχθος, -ου (nom commun) (m)''' : peine ; labeur.<br>
'''μοχθῶ (verbe)''' : être malveillant.<br>
'''μουνάς, -δος (nom commun) (f)''' : Forme ionienne de ''μονάς''.<br>
'''μοῦνος, -ύνη, -ῦνον (adjectif)''' : Forme homérique et ionienne de ''μόνος''.<br>
'''μῶλυ, -ώλυος (nom commun) (n)''' : moly.<br>
'''μῶνος, -η, -ον (adjectif)''' : Forme dorienne de ''μόνος''.<br>
'''μορμολύκειον, -ίου (nom commun) (n)''' : masque.<br>
'''μορφή, -ῆς (nom commun) (f)''' : forme.<br>
'''μόρφωσις, -ώσεως (nom commun) (f)''' : formation.<br>
'''μουσεῖον, -ίου (nom commun) (n)''' : musée.<br>
'''μούσμων, -ονος (nom commun) (m)''' : mouflon.<br>
'''μυδάω (verbe)''' : être moite.<br>
'''μυγαλῆ, -ς (nom commun) (f)''' : musaraigne.<br>
'''μύγις (adverbe)''' : Forme éolienne de ''μόγις''.<br>
'''μυελός, -οῦ (nom commun) (m)''' : moelle.<br>
'''μυζῶ (verbe)''' : sucer.<br>
'''μυθικός, -ή, -όν (adjectif)''' : mythique.<br>
'''μυθιστορία, -ας (nom commun) (f)''' : légende.<br>
'''μυθιστορικός, -ή, -όν (adjectif)''' : légendaire.<br>
'''μῦθος, -ύθου (nom commun) (m)''' : mythe.<br>
'''μυῖα, -ίας (nom commun) (f)''' : mouche.<br>
'''μύκης, -τος (nom commun) (m)''' : Champignon. (Par analogie) Toute excroissance fougueuse. Champignon que se forme à la mèche d’une lampe. Virole d’une gaine. Membre viril.<br>
'''μυκτήρ, -ῆρος (nom commun) (m)''' : museau.<br>
'''μύλη, -ης (nom commun) (f)''' : moulin.<br>
'''μυλεργάτης, -ου (nom commun) (m)''' : meunier.<br>
'''μυλών, -ῶνος (nom commun) (m)''' : meunier.<br>
'''μύξα, -ας (nom commun) (f)''' : morve.<br>
'''μυρίαπους, -δος (nom commun) (m)''' : mille-pattes.<br>
'''μυριάς, -δος (nom commun) (f)''' : .<br>
'''μυρίος, -α, -ον (adjectif)''' : Innombrable, immense ; infini.<br>
'''μύρμηξ, -κος (nom commun) (m)''' : fourmi.<br>
'''μυροϐάλανος, -άνου (nom commun) (f)''' : myrobolan.<br>
'''μύρος, -ου (nom commun) (m)''' : parfum.<br>
'''μυρσίνα, - (nom commun) (f)''' : myrtille.<br>
'''μύρτος, -ου (nom commun) (f)''' : myrtille.<br>
'''μῦς, -ός (nom commun) (m)''' : Rat ; souris. Muscle.<br>
'''μύσταξ, -κος (nom commun) (f)''' : Forme dorienne de ''μάσταξ'' ; moustache.<br>
'''μυστήριον, -ίου (nom commun) (n)''' : sacrement.<br>
'''μυστηριώδης, -ης, -ες (adjectif)''' : relatif aux sacrements.<br>
'''μυστικός, -ή, -όν (adjectif)''' : secret.<br>
'''μυστικῶς (adverbe)''' : secrètement.<br>
'''μυστικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''μυστικός''.<br>
'''μυστικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''μυστικός''.<br>
'''µῦ (nom commun) (n)''' : mu.<br>
'''μύχιος, -α, -ον (adjectif)''' : intime.<br>
'''μυχός, -οῦ (nom commun)''' : cœur.<br>
'''μῶλυ, -ώλυος (nom commun) (n)''' : moly.<br>
'''μῶμαι (verbe)''' : méditer.<br>
'''μῶμος, -ώμου (nom commun) (m)''' : blâme.<br>
'''μῶνυξ, -ώνυχος (nom commun) (m)''' : .<br>
'''μωρός, -ά, -όν (adjectif)''' : Émoussé, hébété. Fade, Insipide. Sot, fou, insensé.<br>
'''μῶρος, -ώρα, -ῶρον (adjectif)''' : Forme attique de ''μωρός''.<br>
'''Μάατ (nom propre) (f)''' : Maât.<br>
'''Μαγδαλά, -ᾶς (nom propre) (f)''' : Magdala.<br>
'''Μαγδαληνή, -ῆς (nom propre) (f)''' : Madeleine.<br>
'''Μαθθαῖος, -ίου (nom propre) (m)''' : Forme alternative de ''Ματθαῖος''.<br>
'''Μαθουσάλα, -ας (nom propre) (m)''' : Mathusalem.<br>
'''Μαῖα, -ίας (nom propre) (f)''' : Maïa.<br>
'''Μαῖρα, -ίρας (nom propre) (f)''' : Maera.<br>
'''Μακάριος, -ίου (nom propre) (m)''' : Macaire.<br>
'''Μακαρία, -ας (nom propre) (f)''' : Macaria.<br>
'''Μάνης, -ου (nom propre) (m)''' : Mani (prophète).<br>
'''Μαντώ, -οῦς (nom propre) (f)''' : Manto.<br>
'''Μάξυος, -ου (nom commun) (m)''' : Berbère.<br>
'''Μαραθών, -ῶνος (nom propre) (m)''' : Marathon.<br>
'''Μαργαρίτα, -ας (nom commun) (f) ''' : Marguerite.<br>
'''Μαρδοχαῖος, -ίου (nom propre) (m)''' : Mardochée.<br>
'''Μάρθα, -ας (nom propre) (f)''' : Martha.<br>
'''Μαρία, -ας (nom propre) (f)''' : Marie.<br>
'''Μασσαλία, -ας (nom propre) (f)''' : Marseille.<br>
'''Μασσαλιώτης, -ου (nom commun) (m)''' : Marseillais.<br>
'''Μασσαλιῶτις, -ώτιδα (nom commun) (f)''' : Marseillaise.<br>
'''Μαῦρος, -ύρου (nom commun) (m)''' : Maure.<br>
'''Μαυσωλεῖον, -ίου (nom propre) (m)''' : Mausolée.<br>
'''Μαύσωλος, -ώλου (nom propre) (m)''' : Mausole.<br>
'''Ματθαῖος, -ίου (nom propre) (m)''' : Matthieu.<br>
'''Μέγαιρα, -ίρας (nom propre) (f)''' : Mégère (une des Érynies).<br>
'''Μέδουσα, -ας (nom propre) (f)''' : Méduse.<br>
'''Μελέαγρος, -άγρου (nom propre) (m)''' : Méléagre.<br>
'''Μελησιγενής, -οῦ (nom propre) (m)''' : Mélésigène.<br>
'''Μέλης (nom propre) (m)''' : Mélès (rivière).<br>
'''Μελέτη, -ης (nom propre) (f)''' : Mélété.<br>
'''Μέλισσα, -ίσσης (nom propre) (f)''' : Mélissa.<br>
'''Μελίτη, -ης (nom propre) (f)''' : Mélite.<br>
'''Μελπομένη, -ης (nom propre) (f)''' : Melpomène.<br>
'''Μελχισέδεκ (nom propre) (m)''' : Melchisédech.<br>
'''Μέμφις, -δος (nom propre) (f)''' : Memphis.<br>
'''Μενίππη, -ης (nom propre) (f)''' : Ménippe.<br>
'''Μένιππος, -ίππου (nom propre) (m)''' : Ménippe.<br>
'''Μεχείρ (nom commun) (m)''' : Méchir.<br>
'''Μήδεια, -ίας (nom propre) (f)''' : Médée.<br>
'''Μηδία, -ας (nom propre) (f)''' : Médie.<br>
'''Μῆδος, -ήδου (nom commun) (m)''' : Mède.<br>
'''Μηλινόη, -ης (nom propre) (f)''' : Mélinoé.<br>
'''Μῆτις, -ήτιδος (nom propre) (f)''' : [[wikt:Métis|Métis]].<br>
'''Μίδας, -ου (nom propre) (m)''' : Midas.<br>
'''Μίθρας, -ου (nom propre) (m)''' : Mithra.<br>
'''Μίθρης, -ου (nom propre) (m)''' : Forme ionienne de ''Μίθρας''.<br>
'''Μιθριδάτης, -ου (nom propre) (m)''' : Mithridate.<br>
'''Μιχαίας, -ου (nom propre) (m)''' : Michée.<br>
'''Μίνως, -ωος (nom propre) (m)''' : Minos.<br>
'''Μίσφρης, -ου (nom propre) (m)''' : Menkhéperrê.<br>
'''Μνεῦις, -ύιδος (nom propre) (m)''' : Mnévis.<br>
'''Μοῖρα, -ίρας (nom propre) (f)''' : Moire.<br>
'''Μοῖσα, -ίσης (nom propre) (f)''' : Forme éolienne de ''Μοῦσα''.<br>
'''Μόνοικος, -ίκου (nom propre) (f)''' : Monaco.<br>
'''Μορμώ, -οῦς (nom propre) (f)''' : Mormo.<br>
'''Μορφεύς, -έως (nom propre) (m)''' : [[wikt:Morphée|Morphée]].<br>
'''Μουάμεδ (nom propre) (m)''' : Mahomet.<br>
'''Μοῦσα, -ύσης (nom propre) (f)''' : Muse.<br>
'''Μοϋσῆς, -έως (nom propre) (m)''' : Forme alternative de ''Μωϋσῆς''.<br>
'''Μυκερίνος, -ου (nom propre) (m)''' : Mykérinos.<br>
'''Μύρμηξ, -ηκος (nom propre) (f)''' : Myrmex.<br>
'''Μῶμος, -ώμου (nom propre) (m)''' : Momos.<br>
'''Μωσῆς, -έως (nom propre) (m)''' : Forme alternative de ''Μωϋσῆς''.<br>
'''Μωϋση, -ης (nom propre) (m)''' : Forme alternative de ''Μωϋσῆς''.<br>
'''Μωϋσῆς, -έως (nom propre) (m)''' : Moïse.<br>
'''Μῶσα, -ώσης (nom propre) (f)''' : Forme dorienne de ''Μοῦσα''.<br>
'''Μῶἁ, -ἁς (nom propre) (f)''' : Forme laconienne de ''Μοῦσα''.<br>
==Ν==
'''ναϝός, -οῦ (nom commun) (m)''' : Forme laconienne de ''ναός''.<br>
'''ναί (particule)''' : oui.<br>
'''ναίχι (adverbe)''' : certes.<br>
'''ναίω (verbe)''' : Habiter. Héberger. Construire ; s’installer quelque part.<br>
'''νᾶνος, -άνου (nom commun) (m)''' : nain.<br>
'''ναός, -οῦ (nom commun) (m)''' : temple.<br>
'''νᾶπυ, -άπυος (nom commun) (f)''' : moutarde (plante).<br>
'''νάρθηξ, -κος (nom commun) (f)''' : férule.<br>
'''ναρθηκοφόρος, -ου (nom commun) (m)''' : licteur.<br>
'''νάρκη, -ης (nom commun) (f)''' : torpeur.<br>
'''νάρκωσις, -όσεως (nom commun) (f)''' : engourdissement.<br>
'''ναρκωτικός, -ή, -όν (adjectif)''' : engourdissant.<br>
'''ναρκῶ (verbe)''' : engourdir.<br>
'''νάσσω (verbe)''' : Presser, fouler. Bourrer, emplir ; encombrer.<br>
'''ναύκληρος, -ήρου (nom commun) (m)''' : .<br>
'''ναῦος, -ύου (nom commun) (m)''' : Forme éolienne de ''ναός''.<br>
'''ναῦς, -εώς (nom commun) (f)''' : navire.<br>
'''ναυτικός, -ή, -όν (adjectif)''' : marin.<br>
'''ναύτης, -ου (nom commun) (m)''' : marin.<br>
'''νάφθα, -ης (nom commun) (f)''' : naphte.<br>
'''νεανιεία, -ας (nom commun) (f)''' : jouvence.<br>
'''νεανίας, -ου (nom commun) (m)''' : jouvenceau.<br>
'''νεᾶνις, -άνιδος (nom commun) (f)''' : jouvencelle.<br>
'''νεανιεύομαι (verbe)''' : .<br>
'''νεανίζω (verbe)''' : .<br>
'''νεηνίης, -ου (nom commun) (m)''' : Forme homérique et ionienne de ''νεανίας''.<br>
'''νεῆνις, -ήνιδος (nom commun) (f)''' : Forme homérique et ionienne de ''νεᾶνις''.<br>
'''νεῖκος, -ίκους (nom commun) (n)''' : Bataille ; combat.<br>
'''νεῖλος, -ίλου (nom commun) (m)''' : rivière de vallée.<br>
'''νεῖος, -ία, -ῖον (adjectif)''' : Forme ionienne de ''νέος''.<br>
'''νεκραγωγός, -οῦ (nom commun) (m)''' : nécragogue.<br>
'''νεκρός, -οῦ (nom commun) (m)''' : mort, cadavre.<br>
'''νεκροψία, -ας (nom commun) (f)''' : nécropsie.<br>
'''νεκυαγωγός, -οῦ (nom commun) (m)''' : nékyagogue.<br>
'''νεκυάμϐατος, -άτου (nom commun) (m)''' : .<br>
'''νεκυδαίμων, -ονος (nom commun) (m)''' : .<br>
'''νεκυηπόλος, - (nom commun) (m)''' : .<br>
'''νέκυια, -ίας (nom commun) (f)''' : descente aux Enfers.<br>
'''νεκυοδαίμων, -ονος (nom commun) (m)''' : .<br>
'''νεκυομαντεῖον, -ίου (nom commun) (n)''' : nékyomantien.<br>
'''νεκυοπομπός, -οῦ (nom commun) (m)''' : nékyopompe.<br>
'''νέκυς, -ος (nom commun) (m)''' : mort.<br>
'''νάννα, -ας (nom commun) (f)''' : tante.<br>
'''νέμος, -ους (nom commun) (n)''' : clairière.<br>
'''νέμω (verbe)''' : Distribuer, partager, attribuer une part, diviser, découper. Attribuer à un troupeau la partie du pâturage où on le mène paître. Habiter. Occuper, détenir. Conduire, gouverner, administrer. Tenir pour, regarder comme (avec double accusatif). (Par suite) Choisir pour, admettre comme.<br>
'''νέννος, -ου (nom commun) (m)''' : oncle.<br>
'''νέομαι (verbe)''' : arriver.<br>
'''νεοσσιά, -ᾶς (nom commun) (f)''' : nid.<br>
'''νεοσσός, -οῦ (nom commun) (m)''' : oisillon, poussin.<br>
'''νέος, -α, -ον (adjectif)''' : Nouveau ; jeune. Neuf.<br>
'''νευρόσπαστον, -ου (nom commun) (n)''' : marionnette.<br>
'''νεῦρον, -ύρου (nom commun) (n)''' : Fibre ; nerf.<br>
'''νευρώδης, -ης, -ες (adjectif)''' : nerveux.<br>
'''νεῦσις, -ύσεως (nom commun) (f)''' : nœud.<br>
'''νεύω (verbe)''' : hocher la tête.<br>
'''νέφος, -ους (nom commun) (n)''' : nuage.<br>
'''νεφρῖτις, -ίτης (nom commun) (f)''' : néphrite (maladie).<br>
'''νεφρός, -οῦ (nom commun) (m)''' : rein.<br>
'''νεώς, -ώ (nom commun) (m)''' : Forme attique de ''ναός''.<br>
'''νέω (verbe)''' : Flotter ; filer la laine.<br>
'''νή (particule)''' : par. (Particule interjective.) (+ nom de divinité a l’accusatif)<br>
'''νῆμα, -ήματος (nom commun) (n)''' : fil.<br>
'''νηματικός, -ή, -όν (adjectif)''' : .<br>
'''νηματώδης, -ης, -ες (adjectif)''' : fibreux.<br>
'''νηός, -οῦ (nom commun) (m)''' : Forme ionienne de ''ναός''.<br>
'''νήπιον, -ίου (nom commun) (n)''' : bébé.<br>
'''νήπιος, -ίου (nom commun) (m)''' : bébé.<br>
'''νήριον, -ίου (nom commun) (n)''' : laurier-rose.<br>
'''νῆσσα, -ήσσης (nom commun) (f)''' : canard.<br>
'''νηστεία, -ας (nom commun) (f)''' : jeûne.<br>
'''νηστεύω (verbe)''' : jeûner.<br>
'''νῆττα, -ήττης (nom commun) (f)''' : Forme attique de ''νῆσσα''.<br>
'''νίζω (verbe)''' : nettoyer.<br>
'''νικέω (verbe)''' : Forme ionienne de ''νικῶ''.<br>
'''νίννη, -ης (nom commun) (f)''' : grand-mère ; belle-mère.<br>
'''νίκη, -ης (nom commun) (f)''' : victoire.<br>
'''νικῶ (verbe)''' : vaincre.<br>
'''νιπτήρ, -ῆρος (nom commun) (m)''' : lavabo.<br>
'''νίπτω (verbe)''' : Variante de ''νίζω''.<br>
'''νίφω (verbe)''' : neiger.<br>
'''νίψ, -φος (nom commun) (f)''' : neige.<br>
'''νόημα, -ήµατος (nom commun) (n)''' : Source de la pensée ; intelligence. Pensée (chose).<br>
'''νόησις, -ήσεως (nom commun) (f)''' : pensée (processus).<br>
'''νοητικός, -ή, -όν (adjectif)''' : Mental ; intellectuel.<br>
'''νοητικῶς (adverbe)''' : Mentalement ; intellectuellement.<br>
'''νοητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''νοητικός''.<br>
'''νοητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''νοητικός''.<br>
'''νοητικώτατα, -, - (adverbe)''' : Superlatif de ''νοητικῶς''.<br>
'''νοητικώτερον, -, - (adverbe)''' : Comparatif de ''νοητικῶς''.<br>
'''νοητός, -ή, -όν (adjectif)''' : pensable.<br>
'''νοητῶς (adverbe)''' : .<br>
'''νοητώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''νοητός''.<br>
'''νοητώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''νοητός''.<br>
'''νομάς, -δος (nom commun) (m/f)''' : nomade.<br>
'''νομοθέτης, -ου (nom commun) (m)''' : législateur.<br>
'''νόμος, -ου (nom commun) (m)''' : Ce qui est attribué en partage. (Par suite) Opinion générale. Maxime. Règle de conduite.<br>
'''νομός, -οῦ (nom commun) (m)''' : Part, portion. (Par extension) Division de territoire. Pâturage, pacage. Herbe, fourrage.<br>
'''νόννος, -ου (nom commun) (m)''' : grand-père ; beau-père.<br>
'''νόος, -ου (nom commun) (m)''' : Faculté de penser.<br>
'''νόσημα, -ήματος (nom commun) (n)''' : infirmité ; fléau.<br>
'''νοσοκομεῖον, -ίου (nom commun) (n)''' : hôpital.<br>
'''νοσοκόμος, -ος, -ν (adjectif)''' : soignant<br>
'''νόσος, -ου (nom commun) (f)''' : maladie.<br>
'''νότιος, -ία, -ιον (adjectif)''' : méridional.<br>
'''νοτιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''νότιως''.<br>
'''νοτιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''νότιως''.<br>
'''νοτιώτατα, -, - (adverbe)''' : Superlatif de ''νότιως''.<br>
'''νοτιώτερον, -, - (adverbe)''' : Comparatif de ''νότιως''.<br>
'''νότιως (adverbe)''' : méridionalement.<br>
'''νότος, -ου (nom commun) (m)''' : sud.<br>
'''νουθετέω (verbe)''' : Admonester ; avertir. Conseiller.<br>
'''vοῦς, -οῦ (nom commun) (m)''' : Variante de ''νόος''.<br>
'''νοῶ (verbe)''' : Penser. Percevoir, noter, observer. Ourdir. Avoir l’intention de. (Linguistique) Signifier, en parlant des mots.<br>
'''νυκτάλωψ, -πία (nom commun) (f)''' : nyctalopie.<br>
'''νυκτερίς, -δος (nom commun) (f)''' : chauve-souris.<br>
'''νύκτερος, -ος, -ον (adjectif)''' : nocturne.<br>
'''νύμφα, -ῦμφας''' : Forme dorienne de ''νύμφη''.<br>
'''νύμφη, -ης (nom commun) (f)''' : nymphe.<br>
'''νυμφόληπτος, -ος, -ον (adjectif)''' : .<br>
'''νυμφιάω (verbe)''' : Être possédé des nymphes ; être en délire.<br>
'''νυμφομανία, -ας (nom commun) (n)''' : nymphomanie.<br>
'''νῦν (adverbe)''' : maintenant.<br>
'''νύξ, -κτός (nom commun) (f)''' : nuit.<br>
'''νυός, -οῦ (nom commun) (f)''' : belle-fille.<br>
'''νῦ (nom commun) (n)''' : nu.<br>
'''νῶμα, -ώματος (nom commun) (n)''' : Forme ionienne de ''νόημα''.<br>
'''νωπός, -ή, -όν (adjectif)''' : frais.<br>
'''νῶσις, -ώσεως (nom commun) (f)''' : Forme ionienne de ''νόησις''.<br>
'''νωτίζω (verbe)''' : tourner le dos.<br>
'''νῶτον, -ώτου (nom commun) (n)''' : (Anatomie) Derrière, dos. (Figuré) Large surface courbe (mer, ciel).<br>
'''νωτοστροφέω (verbe)''' : tourner le dos.<br>
'''νώ (pronom personnel)''' : nous (nous deux).<br>
'''Ναϐαταῖος, -ίου (nom commun) (m)''' : Nabatéen.<br>
'''Ναϐατηνή, -ῆς (nom propre) (f)''' : Nabatea.<br>
'''Ναϊάς, -δος (nom propre) (f)''' : Naïade.<br>
'''Νάρκισσος, -ίσσου (nom propre) (m)''' : Narcisse.<br>
'''Ναυσίθοος, - (nom propre) (m)''' : Nausithoos.<br>
'''Ναυσικάα, -ας (nom propre) (f)''' : Nausicaa.<br>
'''Ναυσίνοoς, - (nom propre) (m)''' : Nausinoos.<br>
'''Νέαιρα, -ίρας (nom propre) (f)''' : Nérée.<br>
'''Νεάπολις, -όλεως (nom propre) (f)''' : Naples.<br>
'''Νεῖλος, -ίλου (nom propre) (m)''' : Nil.<br>
'''Νειλῶτις, -ώτεως (nom propre) (f)''' : Nilotis.<br>
'''Νέμεσις, -έσεως (nom propre) (f)''' : [[wikt:Némésis|Némésis]].<br>
'''Νέσσος, -ου (nom propre) (m)''' : Nessos.<br>
'''Νέστωρ, -ορος (nom propre) (m)''' : Nestor.<br>
'''Νεφέλη, -ης (nom propre) (f)''' : Néphélé.<br>
'''Νεφελοκοκκυγία, -ας (nom propre) (f)''' : Coucouville-les-Nuées. (Ville décrite par Aristophane dans ''Les Oiseaux''.)<br>
'''Νηρεύς, -έως (nom propre) (m)''' : Nérée.<br>
'''Νηρῇς, -δος (nom propre) (f)''' : Néréide.<br>
'''Νίκαια, -ίας (nom propre) (f)''' : Nice.<br>
'''Νίκανδρος, -άνδρου (nom propre) (m)''' : Nicandre.<br>
'''Νίκη, -ης (nom propre) (f)''' : [[wikt:Niké|Niké]].<br>
'''Νικόλαος, -άου (nom propre) (m)''' : Nicolas.<br>
'''Νιόϐη, -ης (nom propre) (f)''' : Niobé.<br>
'''Νότος, -ου (nom propre) (m)''' : [[wikt:Notos|Notos]]. (dieu du vent du sud)<br>
'''Νουμιδία, -ας (nom propre) (f)''' : Numidie.<br>
'''Νύξ, -κτός (nom propre) (f)''' : [[wikt:Nyx|Nyx]].<br>
'''Νῶε (nom propre) (m)''' : Noé.<br>
==Ξ==
'''ξανθóς, -ή, -όν (adjectif)''' : Jaune ; jaunâtre. Blond.<br>
'''ξενοδοχεῖον, -ίου (nom commun) (n)''' : hôtel.<br>
'''ξένος, -η, -ον (adjectif)''' : étranger, insolite.<br>
'''ξένος, -ου (nom commun) (m)''' : étranger ; hôte.<br>
'''ξενών, -ῶνος (nom commun) (f)''' : .<br>
'''ξέσις, -εως (nom commun) (f)''' : .<br>
'''ξεχνῶ (verbe)''' : oublier.<br>
'''ξέω (verbe)''' : gratter ; raser. Polir.<br>
'''ξηρός, -ά, -όν (adjectif)''' : sec.<br>
'''ξῖ (nom commun) (n)''' : xi.<br>
'''ξιφίας, -ου (nom commun) (m)''' : espadon.<br>
'''ξιφίδιον, -ίου (nom commun) (n)''' : dague.<br>
'''ξίφος, -ους (nom commun) (n)''' : glaive.<br>
'''ξύλον, -ου (nom commun) (n)''' : bois (matériau). Tout objet en bois.<br>
'''ξυλόσπογγος, -όγγου (nom commun) (m)''' : tersorium.<br>
'''ξύλωσις, -ώσεως (nom commun) (f)''' : charpente.<br>
'''ξυνός, -ή, -όν (adjectif)''' : Forme ionienne de ''κοινός''.<br>
'''ξύω (verbe)''' : Variante de ''ξέω''.<br>
'''Ξενοφῶν, -τος (nom propre) (m)''' : Xénophon.<br>
'''Ξέρξης, -ου (nom propre) (m)''' : Xerxès.<br>
'''Ξοῦθος, -ύθου (nom propre) (m)''' : Xouthos.<br>
==Ο==
'''ὁ (article défini)''' : le.<br>
'''ὄασις, -άσεως (nom commun) (f)''' : Voie, route ; chemin.<br>
'''ὀγδοήκοντα (adjectif numéral)''' : quatre-vingts.<br>
'''ὀγκάομαι (verbe)''' : .<br>
'''ὄγκος, -ου (nom commun) (m)''' : tumeur.<br>
'''ὄγχνη, -ης (nom commun) (f)''' : poirier.<br>
'''ὅδερος, -έρου (nom commun) (m)''' : ventre.<br>
'''ὁδηγός, -οῦ (nom commun) (m)''' : conducteur.<br>
'''ὁδηγῶ (verbe)''' : conduire.<br>
'''ὁδός, -οῦ (nom commun) (f)''' : Voie, route ; chemin.<br>
'''ὀδούς, -όντος (nom commun) (m)''' : dent.<br>
'''ὀδμή, -ῆς (nom commun) (f)''' : Forme plus ancienne de ''ὀσμή''.<br>
'''ὀδύνη, -ης (nom commun) (f)''' : douleur ; chagrin.<br>
'''ὀδυσάω (verbe)''' : Haïr ; être fâché.<br>
'''ὀδυσσειακός, -ή, -όν (adjectif)''' : odysséen.<br>
'''ὀδύσσομαι (verbe)''' : Être irrité contre quelqu’un.<br>
'''ὄζω (verbe)''' : Exhaler une odeur.<br>
'''ὅθεν (adverbe relatif)''' : d’où (je viens).<br>
'''οἷ (adverbe relatif)''' : là où (je vais).<br>
'''ὀθόνη, -ης (nom commun) (f)''' : toile.<br>
'''οἴ (interjection)''' : hélas ; ouille.<br>
'''οἴκημα, -ήματος (nom commun) (n)''' : résidence.<br>
'''οἰκηματικός, -ή, -όν (adjectif)''' : résidentiel.<br>
'''οἰκημάτιον, -ίου (nom commun) (n)''' : .<br>
'''οἰκητήριον, -ίου (nom commun) (n)''' : domicile.<br>
'''οἰκοπεδικός, -ή, -όν (adjectif)''' : .<br>
'''οἰκόπεδον, -έδου (nom commun) (n)''' : .<br>
'''οἰκοπεδοποίησις, -ήσεως (nom commun) (f)''' : .<br>
'''οἶκος, -ου (nom commun) (m)''' : maison.<br>
'''οἰκοῦμαι (verbe)''' : .<br>
'''οἰκουμένη, -ης (nom commun) (f)''' : .<br>
'''οἰκουμενικός, -ή, -όν (adjectif)''' : .<br>
'''ϝοῖκος, -ίκου (nom commun) (m)''' : Forme ancienne de ''οἶκος''.<br>
'''οἰκεῖος, -ία, -ῖον (adjectif)''' : de la maison.<br>
'''οἰκός, -τος (nom commun) (n)''' : Forme ionienne de ''εἰκός''.<br>
'''οἰκοτροφεῖον, -ίου (nom commun) (n)''' : pensionnat.<br>
'''οικότροφος, -όφου (nom commun) (m/f)''' : pensionnaire.<br>
'''οἶκτος, -ἴκτου (nom commun) (m)''' : pitié.<br>
'''οἰκτρός, -ά, -όν (adjectif)''' : .<br>
'''οἰκτρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''οἰκτρός''.<br>
'''οἰκτρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''οἰκτρός''.<br>
'''οἰκτρότατα, -, - (adverbe)''' : Superlatif de ''οἰκτρῶς''.<br>
'''οἰκτρότερον, -, - (adverbe)''' : Comparatif de ''οἰκτρῶς''.<br>
'''οἰκτρῶς (adverbe)''' : .<br>
'''οἰκῶ (verbe)''' : Habiter ; occuper.<br>
'''οἴμοι (interjection)''' : pauvre de moi.<br>
'''οἶνος, -ἴνου (nom commun) (m)''' : vin.<br>
'''ϝοῖνος, -ίνου (nom commun) (m)''' : Forme dorienne de ''οἶνος''.<br>
'''οἰνοχόη, -ης (nom commun) (f)''' : œnochoé ; échansonne.<br>
'''οἰνοχόος, -ου (nom commun) (m)''' : échanson.<br>
'''ὄϊς, -ΐος (nom commun) (m/f)''' : bélier ; brebis.<br>
'''οἶς, -ός (nom commun) (m/f)''' : Forme attique de ''ὄϊς''.<br>
'''οἷος, -ἵα, -ἷον (adjectif)''' : tel.<br>
'''οἰσοφάγος, -ου (nom commun) (m)''' : œsophage.<br>
'''οἶσος, -ἴσου (nom commun) (m)''' : gattilier.<br>
'''οἶστρος, -ίστρου (nom commun) (m)''' : (au propre) taon. (au figuré) fureur, désir ; passion.<br>
'''οἶτος, -ἴτου (nom commun) (m)''' : destin.<br>
'''οἰφόλης, -ης, ες (adjectif)''' : obscène.<br>
'''οἴφω (verbe)''' copuler.<br>
'''οἰωνοσκοπία, -ας (nom commun) (f)''' : augure.<br>
'''οἰωνοσκόπος, -ου (nom commun) (m)''' : augure.<br>
'''οἰωνός, -οῦ (nom commun) (m)''' : présage, augure.<br>
'''ὀκνείω (verbe)''' : Forme homérique de ''ὀκνέω''.<br>
'''ὀκνέω (verbe)''' : hésiter.<br>
'''ὄκνος, -ου (nom commun) (m)''' : hésitation.<br>
'''ὀκτώ (adjectif numéral)''' : huit.<br>
'''ὄκχος, -ου (nom commun) (m)''' : Forme poétique de ''ὄχος''.<br>
'''ὀλιγάρχης, -ου (nom commun) (m)''' : oligarque.<br>
'''ὀλιγαρχία, -ας (nom commun) (f)''' : oligarchie.<br>
'''ὀλίγος, -η, -ον (adjectif)''' : .<br>
'''ὁλικός, -ή , -όν (adjectif)''' : total.<br>
'''ὁλικῶς (adverbe)''' : totalement.<br>
'''ὁλικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ὁλικός''.<br>
'''ὁλικώτερος, -έρη, -ώτερον (adjectif)''' : Comparatif de ''ὁλικός''.<br>
'''ὁλικώτατα, -, - (adverbe)''' : Superlatif de ''ὁλικῶς''.<br>
'''ὁλικώτερον, -, - (adverbe)''' : Comparatif de ''ὁλικῶς''.<br>
'''ὄλισϐος, -ίσϐου (nom commun) (m)''' : godemichet.<br>
'''ὁλκή, -ῆς (nom commun) (f)''' : attraction.<br>
'''ὄλλυμι (verbe)''' : (Sens actif) Perdre, causer la perte, détruire, annihiler. Mourir. (Sens passif) Périr.<br>
'''ὅλος, -η, -ον (adjectif)''' : entier, complet.<br>
'''ὁλοφύρομαι (verbe)''' : .<br>
'''ὄλπη, -ης (nom commun) (f)''' : .<br>
'''ὀλυμπιακός, -ή, -όν (adjectif)''' : olympien.<br>
'''ὀλυμπικός, -ή, -όν (adjectif)''' : olympique.<br>
'''ὀλύμπιος, -ία, -ύμπιον (adjectif)''' : olympien.<br>
'''ὁμαδικός, -ήν, -όν (adjectif)''' : .<br>
'''ὁμαλός, -ή, -όν (adjectif)''' : lisse.<br>
'''ὁμάς, -δος (nom commun) (f)''' : équipe.<br>
'''ὄμϐρος, -ου (nom commun) (m)''' : tempête de pluie, orage, envoyé par Zeus. Eau. Inondation.<br>
'''ὁμηρικός, -ή, -όν (adjectif)''' : homérique.<br>
'''ὅμηρος, -ήρου (nom commun) (m/f)''' : otage.<br>
'''ὂ μικρόν (nom commun) (n)''' : omicron.<br>
'''ὁμιλία, -ας (nom commun) (f)''' : discours.<br>
'''ὁμιλῶ (verbe)''' : discourir.<br>
'''ὁμίχλη, -ης (nom commun) (f)''' : brouillard.<br>
'''ὄμμα, -τος (nom commun) (n)''' : œil.<br>
'''ὅμοιος, ία, -ον (adjectif)''' : semblable.<br>
'''ὁμοῖος, -, -ῖον (adjectif)''' : Forme homérique, ionienne, et ancienne attique de ''ὅμοιος''.<br>
'''ὁμοίιος, -, - (adjectif)''' : Forme homérique de ''ὅμοιος''.<br>
'''ὁμοιοκαταληξία, -ας (nom commun) (f)''' : rime.<br>
'''ὁμοιοκατάληκτος, -η, -ο (adjectif)''' : rimique.<br>
'''ὁμοιοκαταληκτῶ (verbe)''' : rimer.<br>
'''ὁμοούσιος, -ος, -ον (adjectif)''' : de même substance.<br>
'''ὁμοιούσιος, -ος, -ον (adjectif)''' : de nature semblable.<br>
'''ὁμοιοτέλευτος, -ύτου (nom commun) (m)''' : homéotéleute.<br>
'''ὀμοίωμα, -ώματος (nom commun) (n)''' : effigie.<br>
'''ὁμόνοια, -ας (nom commun) (f)''' : concorde.<br>
'''ὁμός, -ή, -όν (adjectif)''' : Pareil ; semblable.<br>
'''ὁμοῦ (adverbe)''' : En un même lieu, ensemble. (Par suite) Ensemble, à la fois. (Par extension) Auprès, proche.<br>
'''ὀμφαλός, -οῦ (nom commun) (m)''' : nombril.<br>
'''ὀμφή, -ῆς (nom commun) (f)''' : oracle, prophétie.<br>
'''ὀμφής, -ής, -ές : (adjectif)''' : prophétique.<br>
'''ὀμφητήρ, -τρός (nom commun) (m)''' : prophète.<br>
'''ὄνειδος, -ίδους (nom commun) (n)''' : Reproche ; disgrâce.<br>
'''ὀνειρευτής, -οῦ (nom commun) (m)''' : rêveur.<br>
'''ὄνειρος, -ίρου (nom commun) (m)''' : rêve.<br>
'''ὀνειροπόλος, -ου (nom commun) (m)''' : rêvasseur.<br>
'''ὀνειροπωλῶ (verbe)''' : rêvasser.<br>
'''ὀνοκρόταλος, -άλου (nom commun) (m)''' : pélican.<br>
'''ὄνομα, -όματος (nom commun) (n)''' : nom.<br>
'''ὀνοματοποιία, -ας (nom commun) (f)''' : mot imitant un son.<br>
'''ὄνος, -ου (nom commun) (m/f)''' : Âne ; ânesse.<br>
'''ὄνυξ, -χος (nom commun) (m)''' : ongle.<br>
'''ὄνυμα, -ύματος (nom commun) (n)''' : Forme dorienne et éolienne de ''ὄνομα''.<br>
'''ὀνύχιον, -ίου (nom commun) (m)''' : griffe.<br>
'''ὄνω (adverbe)''' : Forme éolienne de ''ἄνω''.<br>
'''ὀξίνης, -ης, -ες (adjectif)''' : aigre.<br>
'''ὀξύα, -ας (nom commun) (f)''' : Hêtre. Arme en bois de hêtre comme un épieu ou un javelot.<br>
'''ὀξύγαλα, -άλακτος (nom commun) (n)''' : petit-lait.<br>
'''ὀξύμωρος, -ος, -ον (adjectif)''' : oxymore.<br>
'''ὀξυπόριον, -ίου (nom commun) (n)''' : carminatif.<br>
'''ὀξύς, -εῖα, -ύ (adjectif)''' : Aigu. Pointu. Tranchant en parlant d’une arme. Piquant en parlant de la sensation (chaleur, froid, douleur), du goût (acidité). Vif. Fin, pénétrant. Prompt à l’action.<br>
'''ὀξύτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ὀξύς''.<br>
'''ὀξύτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ὀξύς''.<br>
'''ὀπαδός, -οῦ (nom commun) (m)''' : fan.<br>
'''ὀπάζω (verbe)''' : .<br>
'''ὀπή, -ῆς (nom commun) (f)''' : Trou ; orifice.<br>
'''ὄπιον, -ίου (nom commun) (n)''' : opium.<br>
'''ὄπισθεν (adverbe)''' : arrière, derrière.<br>
'''ὄπις, -δος (nom commun) (f)''' : (Sens négatif) Revanche, vengeance. (Sens positif) Regard bienveillant, égard. (Religion) Égards dûs aux dieux par les hommes.<br>
'''ὄπις (adverbe)''' : en arrière (sans mouvement).<br>
'''ὀπίσω (adverbe)''' : en arrière (avec mouvement), vers l’arrière.<br>
'''ὁπλή, -ῆς (nom commun) (f)''' : sabot (corne pédestre de certains animaux).<br>
'''ὅπλη, -ης (nom commun) (f)''' : Forme homérique de ''ὁπλή''.<br>
'''ὁπλήεις, -εις, -ης (f)''' : .<br>
'''ὁπλίτης, -ου (nom commun) (m)''' : Soldat pesamment armé.<br>
'''ὅπλον, -ου (nom commun) (n)''' : Outil. (Au pluriel) Armes.<br>
'''ὀποπάναξ, -κος (nom commun) (m)''' : jus de légume.<br>
'''ὀπός, -οῦ (nom commun) (m)''' : jus ; suc.<br>
'''ὀπτασία, -ας (nom commun) (f)''' : fantasme.<br>
'''ὀπτικός, -ή, -όν (adjectif)''' : visuel.<br>
'''ὀπτός, -ή, -όν (adjectif)''' : Rôti, grillé. (Par extension) Cuit. Visible, vu.<br>
'''ὀπτῶ (verbe)''' : Rôtir, griller.<br>
'''ὅραμα, -άματος (nom commun) (n)''' : vision, spectacle.<br>
'''ὄρασις, -άσεως (nom commun) (f)''' : vue.<br>
'''ὁρατός, -ή, -όν, (adjectif)''' : visible.<br>
'''ὁράω (verbe)''' : voir, regarder.<br>
'''ὀρεάς, -δος (nom commun) (f)''' : oréade.<br>
'''ὁρέω (verbe)''' : Forme ionienne de ''ὁράω''.<br>
'''ὄρημι (verbe)''' : Forme éolienne de ''ὁράω''.<br>
'''ὄργανον, -άνου (nom commun) (n)''' : organe, instrument de musique.<br>
'''ὀργασμός, -οῦ (nom commun) (m)''' : point culminant du plaisir sexuel.<br>
'''ὀργάς, -δος (nom commun) (f)''' : .<br>
'''ὀργή, -ῆς (nom commun) (f)''' : colère.<br>
'''ὀργῶ (verbe)''' : enfler, être empli de désir amoureux.<br>
'''ὄρεξις, -έξεως (nom commun) (f)''' : appétit.<br>
'''ὀρθογραφία, -ας (nom commun) (f)''' : écriture correcte.<br>
'''ὀρθόδοξος, -ος, -ον (adjectif)''' : orthodoxe.<br>
'''ὀρθός, -ή, -όν (adjectif)''' : droit.<br>
'''ὀρθότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ὀρθός''.<br>
'''ὀρθότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ὀρθός''.<br>
'''ὀρθῶς (adverbe)''' : droitement.<br>
'''ὁρίζων, -οντος (m)''' : horizon.<br>
'''ὁρίζω (verbe)''' : limiter, borner.<br>
'''ὁρμά, -ᾶς (nom commun) (f)''' : Saut, essor.<br>
'''ὁρμέω (verbe)''' : (Marine) Mouiller, être à l'ancrage.<br>
'''ὁρμή, -ῆς (nom commun) (f)''' : élan (rapidité).<br>
'''ὅρμημα, -ήματος (nom commun) (f)''' : impulsion, désir.<br>
'''ὅρμησις, -ήσεως (nom commun) (f)''' : élan.<br>
'''ὁρμητικός, -ή, -όν (adjectif)''' : impétueux.<br>
'''ὁρμητικῶς (adverbe)''' : impétueusement.<br>
'''ὁρμητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ὁρμητικός''.<br>
'''ὁρμητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ὁρμητικός''.<br>
'''ὁρμητικώτατα, -, - (adverbe)''' : Superlatif de ''ὁρμητικῶς''.<br>
'''ὁρμητικώτερον, -, - (adverbe)''' : Comparatif de ''ὁρμητικῶς''.<br>
'''ὁρμητικότης, -τος (nom commun) (f)''' : impétuosité.<br>
'''ὁρμῶ (verbe)''' : Pousser, démarrer, mettre en mouvement ; presser, se presser ; aller vite.<br>
'''ὄρνεον, -έου (nom commun) (n)''' : oiseau.<br>
'''ὄρνις, -θος (nom commun) (m/f)''' : oiseau.<br>
'''ὄρνυμι (verbe)''' : animer, inciter.<br>
'''ὄρος, -ους (nom commun) (m)''' : Montagne ; colline, hauteur.<br>
'''ὅρος, -ου (nom commun) (m)''' : borne.<br>
'''ὀρός, -οῦ (nom commun) (m)''' : sérum.<br>
'''ὄρρος, -ου (nom commun) (m)''' : (Familier) cul.<br>
'''ὀροφή, -ῆς (nom commun) (f)''' : plafond.<br>
'''ὁρόω (verbe)''' : Forme homérique de ''ὁράω''.<br>
'''ὄρτυξ, -ύκου (nom commun) (m/f)''' : caille.<br>
'''ὀρυκτήρ, -ῆρος (nom commun) (m)''' : mineur.<br>
'''ὄρυξ, -γος (nom commun) (m)''' : oryx.<br>
'''ὀρύσσω (verbe)''' : creuser.<br>
'''ὀρφανία, -ας (nom commun) (f)''' : .<br>
'''ὀρφανίζω (verbe)''' : .<br>
'''ὀρφανιστής, -οῦ (nom commun) (m)''' : .<br>
'''ὀρφανός, -ή, -όν (adjectif)''' : orphelin.<br>
'''ὀρφανός, -οῦ (nom commun) (m)''' : orphelin.<br>
'''ὀρφανοτροφεῖον, -ίου (nom commun) (n)''' : orphelinat.<br>
'''ὄρανος, -άνου (nom commun) (m)''' : Forme éolienne de ''οὐρανός''.<br>
'''ὀρείχαλκος, -άλκου (nom commun) (m)''' : orichalque.<br>
'''ὄρφνα, -ας (nom commun) (f)''' : Forme dorienne de ''ὄρφνη''.<br>
'''ὄρφνη, -ης (nom commun) (f)''' : obscurité.<br>
'''ὀρφνός, -ή, -όν (adjectif)''' : obscur.<br>
'''ὀρφνότατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ὀρφνός''.<br>
'''ὀρφνότερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ὀρφνός''.<br>
'''ὀρφνῶς (adverbe)''' : obscurément.<br>
'''ὀρχήστρα, -ας (nom commun) (f)''' : place du chœur.<br>
'''ὀρχηστρίς, -δος (nom commun) (f)''' : danseuse.<br>
'''ὄρχις, -εως (nom commun) (m/f)''' : (Au masculin) Testicule, ovaire, orchidée. (Au féminin) Sorte d’olive.<br>
'''ὀρχοῦμαι (verbe)''' : danser.<br>
'''ὄσδω (verbe)''' : Forme dorienne de ''ὄζω''.<br>
'''ὀσμή, -ῆς (nom commun) (f)''' : odeur.<br>
'''ὀστέον, -ου (nom commun) (n)''' : os ; noyau.<br>
'''ὀστοῦν, -οῦ (nom commun) (n)''' : Forme attique de ''ὀστέον''.<br>
'''ὀστεῦν, -εῦ (nom commun) (n)''' : Forme poétique de ''ὀστέον''.<br>
'''ὄστιον, -ίου (nom commun) (n)''' : Forme éolienne de ''ὀστέον''.<br>
'''ὅς, ἥ, ὅ''' : (adjectif démonstratif) Celui-ci, celle-ci, ceci. (pronom relatif) Qui, lequel, laquelle.<br>
'''ὄστρειον, -ίου (nom commun) (n)''' : Forme de ''ὄστρεον''.<br>
'''ὄστρεον, -έου (nom commun) (n)''' : huître.<br>
'''ὀσφραίνομαι (verbe)''' : sentir.<br>
'''ὄσφρησις, -ήσεως (nom commun) (f)''' : odorat.<br>
'''ὀσφυαλγία, -ας (nom commun) (f)''' : lumbago.<br>
'''ὀσφῦς, -ύος (nom commun) (f)''' : flanc.<br>
'''οὐ- (préfixe)''' : préfixe privatif.<br>
'''οὐαί (interjection)''' : ah.<br>
'''οὖας, -τός (nom commun) (n)''' : Forme homérique de ''οὖς''.<br>
'''οὐδείς, -μία, -έν (adjectif indéfini ; pronom indéfini)''' : Aucun, personne.<br>
'''οὐδέποτε (adverbe)''' : jamais.<br>
'''οὐδέτερος, -έρα, -έτερον (adjectif)''' : ni l’un ni l’autre.<br>
'''οὕδωρ, -ατος (nom commun) (n)''' : Forme béotienne de ''ὕδωρ''.<br>
'''οὐκί (adverbe)''' : Forme attique et ionienne de ''οὐ''<br>
'''οὐλή, -ῆς (nom commun) (f)''' : cicatrice.<br>
'''οὔνομα, -όματος (nom commun) (n)''' : Forme homérique et ionienne de ''ὄνομα''.<br>
'''οὖθαρ, -ὔθατος (nom commun) (n)''' : Mamelle. (Anatomie) Pis.<br>
'''οὔποτε (adverbe)''' : jamais.<br>
'''οὐροδοχεῖον, -ίου (nom commun) (n)''' : pot de chambre.<br>
'''οὖρον, -ὔρου (nom commun) (n)''' : urine.<br>
'''οὖρος, -ὔρου (nom commun) (m)''' : gardien.<br>
'''οὗ (adverbe relatif)''' : où (je suis).<br>
'''οὐ (particule)''' (Devient ''οὐκ'' devant un mot commençant par une voyelle à esprit doux, et ''οὐχ'' devant un mot commençant par une voyelle à esprit rude.) : non.<br>
'''οὖν (adverbe)''' : Sans doute, réellement, en effet. Donc, eh bien.<br>
'''οὐρά, -ᾶς (nom commun) (f)''' : queue.<br>
'''οὐράνη, -ης (nom commun) (f)''' : pot de chambre.<br>
'''οὐράνιος, -α, -ον (adjectif)''' : céleste.<br>
'''οὐρανός, -οῦ (nom commun) (m)''' : ciel.<br>
'''οὔρησις, -ήσεως (nom commun) (f)''' : miction.<br>
'''οὐρητρίς, -δος (nom commun) (f)''' : pot de chambre.<br>
'''οὖρον, -ὔρου (nom commun) (n)''' : urine.<br>
'''οὐρῶ (verbe)''' : uriner.<br>
'''οὐσία, -ας (nom commun) (f)''' : (Philosophie) Essence, substance, être. Élément, substance première. Biens, fortune, richesse.<br>
'''οὐσιαστικόν, -οῦ (nom commun) (n)''' : substantif.<br>
'''οὐσίη, -ης (nom commun) (f)''' : Forme ionienne de ''οὐσία''.<br>
'''οὐσιώδης, -ης, -ες (adjectif)''' : essentiel.<br>
'''οὖς, ὠτός (nom commun) (n)''' : oreille.<br>
'''οὔτις, -ις, -ι (pronom)''' : personne.<br>
'''οὐχί (adverbe)''' : Forme attique et homérique de ''οὐ''<br>
'''ὀφείλημα, -ήματος (nom commun) (n)''' : dette.<br>
'''ὀφείλω (verbe)''' : devoir de l'argent.<br>
'''ὀφέλλω (verbe)''' : Forme homérique et de ''ὀφείλω''.<br>
'''ὄφελος, -έλους (nom commun) (n)''' : .<br>
'''ὀφθαλμός, -οῦ (nom commun) (m)''' : œil.<br>
'''ὄφις, -εως (nom commun) (m)''' : serpent.<br>
'''ὀφρῦς, -ύος (nom commun) (f)''' : sourcil.<br>
'''ὀχεύω (verbe)''' copuler.<br>
'''ὄχημα, -ήματος (nom commun) (n)''' : véhicule.<br>
'''ϝόχος, -ου (nom commun) (m)''' : Forme ancienne de ''ὄχος''.<br>
'''ὄχθη, -ης (nom commun) (f)''' : Rive ; berge.<br>
'''ὀχλοκρατία, -ας (nom commun) (f)''' : ochlocratie.<br>
'''ὄχλος, -ου (nom commun) (m)''' : Foule ; multitude. Tumulte d'une foule.<br>
'''ὄχνη, -ης (nom commun) (f)''' : poire.<br>
'''ὄχος, -ου (nom commun) (m)''' : Réceptacle, abri. Tout ce qui sert à transporter, véhicule.<br>
'''-οχος, -όχου (suffixe) (m)''' : .<br>
'''ὀχυρός, -ά, -όν (adjectif)''' : ferme.<br>
'''ὀχυρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ὀχυρός''.<br>
'''ὀχυρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ὀχυρός''.<br>
'''ὀχυρῶς (adverbe)''' : fermement.<br>
'''ὀχύρωμα, -ώματος (nom commun) (n)''' : forteresse.<br>
'''ὀχυρῶ (verbe)''' : fortifier.<br>
'''ὀχῶ (verbe)''' : .<br>
'''ὄψις, -εως (nom commun) (f)''' : vue. (perception visuelle)<br>
'''ὄψον, -ου (nom commun) (n)''' : (Cuisine) Plat cuisiné, mets. Assaisonnement, sauce. Délicatesse. (Attique) Poisson.<br>
'''ὀψοποιός, -οῦ (nom commun) (m)''' : cuisinier.<br>
'''ὀψώνης, -ου (nom commun) (m)''' : .<br>
'''ὀψώνιον, -ίου (nom commun) (n)''' : Provisions, vivres. Salaire.<br>
'''ὀψωνῶ (verbe)''' : .<br>
'''ὄψ, -πός (nom propre) (f)''' : Voix. Parole, discours. Œil. (Par métonymie) Visage.<br>
'''Ὀδρύσης, -ου (nom commun) (m)''' : Odryse.<br>
'''Ὀδύσσεια, -ίας (nom propre) (f)''' : Odyssée.<br>
'''Ὀδυσσεία, -ας (nom propre) (f)''' : Forme alternative de ''Ὀδύσσεια''.<br>
'''Ὀδυσσεύς, -έως (nom propre) (m)''' : Ulysse.<br>
'''Ὀζυμάνδιος, -ίου (nom propre) (m)''' : Ozymandias.<br>
'''Ὄθων, -ου (nom propre) (m)''' : Othon.<br>
'''Οἰδίπους, -οδος (nom propre) (m)''' : Œdipe.<br>
'''Οἰκλῆς, -έους (nom propre) (m)''' : Oïclès.<br>
'''Οἰνεύς, -έως (nom propre) (m)''' : Œnée.<br>
'''Οἰνόμαος, -άου (nom propre) (m)''' : Œnomaos.<br>
'''Οἰταῖος, -ίη, -ῖον (adjectif)''' : œtéen.<br>
'''Οἴτη, -ης (nom propre) (f)''' : Œta.<br>
'''Ὄλθφες, - (nom propre) (m)''' : Olivier.<br>
'''Ὀλυμπιάς, -δος (nom propre) (f)''' : olympiade.<br>
'''Ὀλύμπια, -ίων (nom propre) (n)''' : Jeux olympiques.<br>
'''Ὄλυμπος, -ύμπου (nom propre) (m)''' : Olympe.<br>
'''Ὀλυσσεύς, -έως (nom propre) (m)''' : Forme de ''Ὀδυσσεύς''.<br>
'''Ὀλυττεύς, -έως (nom propre) (m)''' : Forme de ''Ὀδυσσεύς''.<br>
'''Ὅμηρος, -ήρου (nom propre) (m)''' : Homère. (poète grec)<br>
'''Ὄνειρος, -ίρου (nom propre) (m)''' : Oniros.<br>
'''Ὀννῶφρις, -ίδος (nom propre) (m)''' : Onnophris.<br>
'''Ὀνούφριος, -ίου (nom propre) (m)''' : Onuphre.<br>
'''Ὄρανος, -άνου (nom propre) (m)''' : Forme éolienne de ''Οὐρανός''.<br>
'''Ὀρσηίς, -δος (nom propre) (f)''' : Orséis (épouse d'Hellen).<br>
'''Ὄρθρος, -ου (nom propre) (m)''' : Orthros.<br>
'''Ὀρφεύς, -έως (nom propre) (m)''' : Orphée.<br>
'''Ὄσιρις, -ίριδος (nom propre) (m)''' : [[wikt:Osiris|Osiris]].<br>
'''Οὐδαῖος, -ίου (nom propre) (m)''' : Udée.<br>
'''Οὑδυσσεύς, -έως (nom propre) (m)''' : Forme de ''Ὀδυσσεύς''.<br>
'''Οὐλιξεύς, -έως (nom propre) (m)''' : Forme de ''Ὀδυσσεύς''.<br>
'''Οὐλίξης, -ους (nom propre) (m)''' : Forme de ''Ὀδυσσεύς''.<br>
'''Οὐόλκος, -ου (nom commun) (m)''' : Volque.<br>
'''Οὐρανία, -ας (nom propre) (f)''' : Uranie.<br>
'''Οὐρανός, -οῦ (nom propre) (m)''' : [[wikt:Ouranos|Ouranos]].<br>
'''Ὀφηλία, -ας (nom propre) (f)''' : Ophélie.<br>
'''Ὀφιοῦχος, -ύχου (nom propre) (f)''' : Serpentaire.<br>
'''Ὄφις, -εως (nom propre) (m)''' : Serpent.<br>
'''Ὀφόις -πως (nom propre) (m)''' : Oupouaout.<br>
==Π==
'''παγά, -ᾶς (nom commun) (f)''' : Forme dorienne de ''πηγή''.<br>
'''παγίδευσις, -ύσεως (nom commun) (f)''' : .<br>
'''παγιδεύω (verbe)''' : piéger.<br>
'''παγίς, -δος (nom commun) (f)''' : piège.<br>
'''παγκόσμιος, -α, -ον (adjectif)''' : universel.<br>
'''πάγκοσμος, -όσμου (nom commun) (m)''' : univers.<br>
'''πάγος, -ου (nom commun) (m)''' : glace (eau à l'état solide).<br>
'''πάγουρος, -ύρου (nom commun) (m)''' : crabe.<br>
'''πάγχυ (adverbe)''' : totalement.<br>
'''παθοποιός, -ός, -όν (adjectif)''' : Qui afflige le corps ou l’âme.<br>
'''πάθος, -ους (nom commun) (n)''' : Ce que l’on éprouve. Évènement, conjoncture. État de l’âme agitée par diverses circonstances.<br>
'''παιάν, -ᾶνος (nom commun) (m)''' : péan.<br>
'''παίγνιον, -ίου (nom commun) (n)''' : jeu.<br>
'''παιδαγωγός, -οῦ (nom commun) (m)''' : éducateur.<br>
'''παιδάριον, -ίου (nom commun) (n)''' : gamin.<br>
'''παιδεραστής, -οῦ (nom commun) (m)''' : amant de garçon.<br>
'''παιδιά, -ᾶς (nom commun) (f)''' : jeu.<br>
'''παιδίον, -ου (nom commun) (n)''' : petit enfant.<br>
'''παιδοτρίϐης, -ου (nom commun) (m)''' : maître de gymnastique.<br>
'''παίδων παῖδες (nom commun) (m/f)''' : Petits-fils/Petites-filles.<br>
'''παίζω (verbe)''' : jouer.<br>
'''παίκτης, -ου (nom commun) (m)''' : joueur.<br>
'''παῖς, -δός (nom commun) (m/f)''' : enfant ; jeune serviteur.<br>
'''παιωνία, -ας (nom commun) (f)''' : pivoine.<br>
'''παιών, -ος (nom commun) (m)''' : physicien.<br>
'''παλαιός, -ά, -όν (adjectif)''' : ancien.<br>
'''παλιγγενεσία, -ας (nom commun) (f)''' : Renaissance, résurrection. Régénération.<br>
'''παλιγγενεσιακός, -ά, -όν (adjectif)''' : palingénésiaque.<br>
'''πάλιν (adverbe)''' : encore.<br>
'''παλλακή, -ῆς (nom commun) (f)''' : concubine.<br>
'''παλλακίς, -δος (nom commun) (f)''' : concubine.<br>
'''πάλλαξ, -κος (nom commun) (m/f)''' : jeune enfant.<br>
'''πάλληξ, -κος (nom commun) (m/f)''' : Forme ionienne de ''πάλλαξ''.<br>
'''πάλλω (verbe)''' : vibrer.<br>
'''παλμός, -οῦ (nom commun) (m)''' : vibration.<br>
'''παλός, -οῦ (nom commun) (m/f)''' : forme dorienne de ''πηλός''.<br>
'''παμμεγεθέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''παμμεγέθης''.<br>
'''παμμεγεθέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''παμμεγέθης''.<br>
'''παμμεγέθης, -ης, -ες (adjectif)''' : immense.<br>
'''παμμεγέθως (adverbe)''' : immensément.<br>
'''πάμπαν (adverbe)''' : totalement.<br>
'''πάμφλεκτος, -ος, -ον (adjectif)''' : .<br>
'''πανάκεια, -ας (nom commun) (f)''' : remède universel.<br>
'''πάναξ, -κος (nom commun) (m)''' : plante médicinale.<br>
'''πανδοκεῖον, -ίου (nom commun) (n)''' : auberge.<br>
'''πανδοκεύς, -έως (nom commun) (m)''' : aubergiste.<br>
'''παράδοσις, -όσεως (nom commun) (f)''' : transmission ; tradition.<br>
'''πανήγυρις, -ύρεως (nom commun) (f)''' : réunion.<br>
'''πάνθηρ, -ος (nom commun) (m)''' : panthère.<br>
'''πανέλοψ, -πος (nom commun) (m)''' : Forme éolienne et dorienne de ''πηνέλοψ''.<br>
'''πανοῦργος, -ύργα, -ῦργον (adjectif)''' : .<br>
'''παντοπωλεῖον, -ίου (nom commun) (n)''' : épicerie.<br>
'''παντοπώλης, -ου (nom commun) (m)''' : épicier.<br>
'''πάνυ (adverbe)''' : totalement.<br>
'''πάππας, -ου (nom commun) (m)''' : papa.<br>
'''πάππος, -ου (nom commun) (m)''' : papi.<br>
'''πάπυρος, -ύρου (nom commun) (m)''' : papyrus.<br>
'''παρά (adverbe ; préposition)''' : (avec le génitif) D’auprès de, du côté de. (avec le datif) Auprès avec l’idée de toute absence de mouvement. Chez, dans, en. (Avec l’accusatif) Auprès de, vers avec l’idée de mouvement.<br>
'''παρά- (préfixe)''' : Auprès. Vers. Le long de. Contre. En détournant.<br>
'''παραϐάλλω (verbe)''' : s’emparer de.<br>
'''παράδειγμα, -ίγματος (nom commun) (n)''' : modèle.<br>
'''παραδειγματικός, -ή, -όν (adjectif)''' : modèle.<br>
'''παράδεισος, -ίσου (nom commun) (m)''' : parc ; paradis.<br>
'''παραδέχομαι (verbe)''' : admettre.<br>
'''παραδίδωμι (verbe)''' : transmettre ; remettre.<br>
'''παράθυρος, -ου (nom commun) (f)''' : Porte de côté ; porte dérobée.<br>
'''παρακινῶ (verbe)''' : .<br>
'''παρακλαυσίθυρον, -ύρου (nom commun) (n)''' : paraclausithuron.<br>
'''παραλείπω (verbe)''' : omettre.<br>
'''παράλειψις, -ίψεως (nom commun) (f)''' : omission.<br>
'''πάρδαλις, -άλεως (nom commun) (f)''' : léopard.<br>
'''παράλληλος, -η, -ον (adjectif)''' : Porte de côté ; porte dérobée.<br>
'''παραλήρησις, -ήσεως (nom commun) (f)''' : délire.<br>
'''παράμεσος, -ου (nom commun) (m)''' : annulaire.<br>
'''παραμυθέομαι (verbe)''' : exhorter.<br>
'''παραμύθιον, -ίου (nom commun) (m)''' : exhortation.<br>
'''παραποίησις, -ήσεως (nom commun) (f)''' : contrefaçon.<br>
'''παραποιῶ (verbe)''' : contrefaire.<br>
'''παράρτημα, -ήματος (nom commun) (n)''' : annexe.<br>
'''παρασάγγης, -ου (nom commun) (m)''' : parasange. (env. 6 km)<br>
'''παράσιτος, -ος, -ον (adjectif)''' : Qui sert de mets en surplus ; Parasite.<br>
'''παραίσθησις, -ήσεως (nom commun) (f)''' : illusion.<br>
'''παράστασις, -άσεως (nom commun) (f)''' : représentation.<br>
'''παραστράτημα, -ήματος (nom commun) (n)''' : incartade.<br>
'''παρειά, -ᾶς (nom commun) (f)''' : joue.<br>
'''παρελθών, -όντος (nom commun) (m)''' : passé.<br>
'''παρεξήγησις, -ήσεως (nom commun) (f)''' : malentendu.<br>
'''παρεξηγῶ (verbe)''' : méprendre.<br>
'''παρεργάτης, -ου (nom commun) (m)''' : assistant.<br>
'''παρεύνασθαι (verbe)''' : représenter.<br>
'''πάρευνος, -ος, -ον (adjectif)''' : .<br>
'''παρθενεία, -ας (nom commun) (f)''' : virginité .<br>
'''παρθένειος, -α, -ον (adjectif)''' : virginal.<br>
'''παρθενικός, -ή, -όν (adjectif)''' : de vierge.<br>
'''παρθενικῶς (adverbe)''' : virginalement.<br>
'''παρθενικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''παρθενικός''.<br>
'''παρθενικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''παρθενικός''.<br>
'''παρθενικώτατα, -, - (adverbe)''' : Superlatif de ''παρθενικῶς''.<br>
'''παρθενικώτερον, -, - (adverbe)''' : Comparatif de ''παρθενικῶς''.<br>
'''παρθένος, -ος, -ον (adjectif)''' : vierge.<br>
'''παρθενεύω (verbe)''' : devenir jeune fille.<br>
'''παρθενών, -ῶνος (nom commun) (m)''' : parthénon.<br>
'''παρθικός, -ή, -όν (adjectif)''' : parthe.<br>
'''παρθιστί (adverbe)''' : en parthe.<br>
'''παρίσθμιον, -ίου (nom commun) (n)''' : (anatomie) amygdale.<br>
'''παρόρμησις, -ήσεως (nom commun) (f)''' : impulsion.<br>
'''παρορμητικός, -ή, -όν (adjectif)''' : impulsif.<br>
'''παρορμητικῶς (adverbe)''' : impulsivement.<br>
'''παρορμητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''παρορμητικός''.<br>
'''παρορμητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''παρορμητικός''.<br>
'''παρορμητικώτατα, -, - (adverbe)''' : Superlatif de ''παρορμητικῶς''.<br>
'''παρορμητικώτερον, -, - (adverbe)''' : Comparatif de ''παρορμητικῶς''.<br>
'''παρορμῶ (verbe)''' : impulser.<br>
'''παρσένος, -ος, -ον (adjectif)''' : Forme laconienne de ''παρθένος''.<br>
'''παρών, -όντος (nom commun) (m)''' : présent.<br>
'''παρωνυχία, -ας (nom commun) (f)''' : panaris.<br>
'''πᾶς, -σα, -ᾶν (adjectif)''' : (Au singulier) Chaque, chacun. Tout entier, tout. (Au pluriel) Tous.<br>
'''πάσσω (verbe)''' : .<br>
'''παστά, -ᾶς (nom commun) (f)''' : pâte.<br>
'''παστός, -ή, -όν (adjectif)''' : .<br>
'''πάσχω (verbe)''' : Être affecté de telle ou telle façon, telle ou telle affection, sensation ou sentiment.<br>
'''πατήρ, -ρός (nom commun) (m)''' : père.<br>
'''πάτος, -ου (nom commun) (m)''' : sentier.<br>
'''πατριά, -ᾶς (nom commun) (f)''' : tribu, famille.<br>
'''πατριάρχης, -ου (nom commun) (m)''' : patriarche.<br>
'''πατρίς, -δος (nom commun) (f)''' : patrie.<br>
'''πατροφονεύς, -έως (nom commun) (m)''' : patricide.<br>
'''πατροφόνος, -ου (nom commun) (m)''' : patricide.<br>
'''πατῶ (verbe)''' : Manger, absorber. Fouler aux pieds. Fouler le sol.<br>
'''παῦρος -ύρα, -ῦρον (adjectif)''' : En petit nombre. (Par extension) Petit, court.<br>
'''παῦσις, -ύσεως (nom commun) (f)''' : cessation.<br>
'''παύω (verbe)''' : cesser.<br>
'''πάφλασμα, -άσματος (nom commun) (n)''' : .<br>
'''πάχνη, -ης (nom commun) (f)''' : givre.<br>
'''παχύς, -εῖα, -ύ (adjectif)''' : Épais, gras.<br>
'''πεδά (préposition)''' : Forme arcado-chypriote, dorienne et éolienne de ''μετά''.<br>
'''πεδιάς, -δος (nom commun) (f)''' : plaine.<br>
'''πεδίον, -ου (nom commun) (n)''' : champ.<br>
'''πέζα, -ης (nom commun) (f)''' : cheville du pied.<br>
'''πεζός, -ή, -όν (adjectif)''' : piéton.<br>
'''πεζῶς (adverbe)''' : .<br>
'''πεζώτατα, -, - (adverbe)''' : Superlatif de ''πεζῶς''.<br>
'''πεζώτερον, -, - (adverbe)''' : Comparatif de ''πεζῶς''.<br>
'''πεζώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''πεζός''.<br>
'''πεζώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''πεζός''.<br>
'''πείθομαι (verbe)''' : persuader ; suivre.<br>
'''πείθω (verbe)''' : convaincre ; persuader.<br>
'''πεῖνα, -ίνας (nom commun) (f)''' : faim.<br>
'''πεινῶ (verbe)''' : avoir faim.<br>
'''πεῖρα, -ίρας (nom commun) (f)''' : Essai, tentative ; épreuve.<br>
'''πειράζω (verbe)''' : Essayer ; tenter.<br>
'''πειράομαι (verbe)''' : essayer.<br>
'''πειρασμός, -οῦ (nom commun) (m)''' : Épreuve, essai ; expérience. Tentation.<br>
'''πειρατής, -οῦ (nom commun) (m)''' : pirate.<br>
'''πειρῶ (verbe)''' : .<br>
'''πελεκάν, -ᾶνος (nom commun) (m)''' : pélican.<br>
'''πελεκῖνος, -ίνου (nom commun) (m)''' : pélican.<br>
'''πέλεκυς, -έκεως (nom commun) (m)''' : hache.<br>
'''πέλμα, -τος (nom commun) (n)''' : Plante du pied ; tige des pommes et des poires.<br>
'''πελώριος, -α, -ον (adjectif)''' : prodigieux.<br>
'''πελωρίς, -δος (nom commun) (f)''' : moule.<br>
'''πέλωρ, -ος (nom commun) (n)''' : prodige.<br>
'''πέμμα, -τος (nom commun) (n)''' : gâteau.<br>
'''πεμπτουσία, -ας (nom commun) (f)''' : quintessence.<br>
'''πεντάφυλλον, -ύλλου (nom commun) (n)''' : potentille.<br>
'''πέντε (adjectif numéral)''' : cinq.<br>
'''πεντήκοντα (adjectif numéral)''' : cinquante.<br>
'''πενθερά, -ᾶς (nom commun) (f)''' : belle-mère.<br>
'''πενθερός, -οῦ (nom commun) (m)''' : beau-père.<br>
'''πένθος, -ους (nom commun) (n)''' : deuil.<br>
'''πέος, -ους (nom commun) (n)''' : pénis.<br>
'''πέπερι, -τος (nom commun) (n)''' : poivre.<br>
'''πέπλος, -ου (nom commun) (m)''' : péplos.<br>
'''πέπλωμα, -ώματος (nom commun) (n)''' : robe.<br>
'''πεπτός, -ή, -όν (adjectif)''' : cuit.<br>
'''πέπων, -ονος (nom commun) (m)''' : melon.<br>
'''περαίνω (verbe)''' : Finir, conclure.<br>
'''πέρασμα, -άσματος (nom commun) (n)''' : passage.<br>
'''πέρας, -τος (nom commun) (n)''' : Fin, limite.<br>
'''πέρδιξ, -ικος (nom commun) (m/f)''' : perdrix.<br>
'''πέρδομαι (verbe)''' : péter (faire un pet).<br>
'''περιϐόλιον, -ίου (nom commun) (n)''' : .<br>
'''περιδέραιον, -ου (nom commun) (n)''' : collier (bijou).<br>
'''περιδέραιος, -ος, -ου (adjectif)''' : .<br>
'''περίζωμα, -ώματος (nom commun) (n)''' : caleçon.<br>
'''περιήγησις, -ήσεως (nom commun) (f)''' : Voyage ; parcours.<br>
'''περιηγοῦμαι (verbe)''' : Voyager ; parcourir.<br>
'''περιηγητής, -οῦ (nom commun) (m)''' : voyageur.<br>
'''περιλαμϐάνω (verbe)''' : .<br>
'''περίληψις, -ήψεως (nom commun) (f)''' : résumé.<br>
'''περίναιον, -ίου (nom commun) (n)''' : périnée.<br>
'''περιπατητικός, -ή, -όν (adjectif)''' : péripatéticien.<br>
'''περιπατῶ (verbe)''' : Circuler, aller et venir ; se promener (Par extension) (Figuré) Se comporter, se conduire, vivre de telle ou de telle manière.<br>
'''περιπέτεια, -ας (nom commun) (f)''' : aventure.<br>
'''περιπίπτω (verbe)''' : tomber autour, sur.<br>
'''περιποιοῦμαι (verbe)''' : s'occuper de.<br>
'''περιποιῶ (verbe)''' : .<br>
'''περιστερά, -ᾶς (nom commun) (f)''' : pigeon, colombe.<br>
'''περιστερός, -οῦ (nom commun) (m)''' : pigeon, colombe. (Pour un mâle de ces espèces.)<br>
'''περιστερεών, -ῶνος (nom commun) (m)''' : pigeonnier, colombier.<br>
'''περιφρονῶ (verbe)''' : mépriser.<br>
'''περιφρούρησις, -ήσεως (nom commun) (f)''' : protection.<br>
'''περιφρουρῶ (verbe)''' : protéger.<br>
'''περκνός, -ή, -όν (adjectif)''' : noirâtre.<br>
'''περκνότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''περκνός''.<br>
'''περκνότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''περκνός''.<br>
'''περκνότατα, -, - (adverbe)''' : Superlatif de ''περκνῶς''.
'''περκνότερον, -, - (adverbe)''' : Comparatif de ''περκνῶς''.
'''περκνῶς (adverbe)''' : noirâtrement.<br>
'''πέρνημι (verbe)''' : Exploiter, vendre. (Au passif) Être vendu.<br>
'''περόνη, -ης (nom commun) (f)''' : agrafe, clavette.<br>
'''περῶ (verbe)''' : passer.<br>
'''πέσσω (verbe)''' : Digérer ; cuire. Mûrir.<br>
'''πετάννυμι (verbe)''' : .<br>
'''πέτασος, -άσου (nom commun) (m)''' : pétase.<br>
'''πετεεινός, -ή, -όν (adjectif)''' : Forme homérique de ''πετεινός''.<br>
'''πετεινός, -ή, -όν (adjectif)''' : capable de voler.<br>
'''πετεσοũχος, -ύχου (nom commun) (m)''' : petsuchos.<br>
'''πετηνός, -ή, -όν (adjectif)''' : Forme ionienne de ''πετεινός''.<br>
'''πετῶ (verbe)''' : voler (planer dans le ciel).<br>
'''πεύκη, -ης (nom commun) (f)''' : pin.<br>
'''πεύθομαι (verbe)''' : Forme poétique de ''πυνθάνομαι''.<br>
'''πέψις, -εως (nom commun) (f)''' : digestion.<br>
'''πηγή, -ῆς (nom commun) (f)''' : source.<br>
'''πήγνυμι (verbe)''' : .<br>
'''πηδάλιον, -ίου (nom commun) (n)''' : talaria.<br>
'''πήδημα, -ήματος (nom commun) (n)''' : saut.<br>
'''πηδόν, -οῦ (nom commun) (n)''' : .<br>
'''πηδός, -οῦ (nom commun) (f)''' : .<br>
'''πηδῶ (verbe)''' : sauter.<br>
'''πῇ (adverbe interrogatif)''' : par où.<br>
'''πήληξ, -κος (nom commun) (m)''' : .<br>
'''πηλός, -οῦ (nom commun) (m/f)''' : boue.<br>
'''πήμων, -ων, -ον (adjectif)''' : nuisible.<br>
'''πηνέλοψ, -πος (nom commun) (m)''' : canard sauvage.<br>
'''πήνη, -ης (nom commun) (f)''' : bobine.<br>
'''πηνήκη, -ης (nom commun) (f)''' : perruque.<br>
'''πηνηκίζω (verbe)''' : .<br>
'''πηνήκισμα, -ίσματος (nom commun) (n)''' : .<br>
'''πῆνος, -ήνου (nom commun) (m)''' : .<br>
'''πῆξις, -ήξεως (nom commun) (f)''' : .<br>
'''πήρα, -ας (nom commun) (f)''' : besace.<br>
'''πήριξ, -ικος (nom commun) (m/f)''' : Forme crétoise de ''πέρδιξ''.<br>
'''πῆχυς, -ήχεως (nom commun) (m)''' : avant-bras.<br>
'''πιέζω (verbe)''' : Presser ; appuyer.<br>
'''πίεσις, -έσεως (nom commun) (f)''' : pression.<br>
'''πῖ (nom commun) (n)''' : pi.<br>
'''πίθακος, -άκου (nom commun) (m)''' : Forme dorienne de ''πίθηκος''.<br>
'''πιθανός, -ή, -όν (adjectif)''' : probable.<br>
'''πιθανῶς (adverbe)''' : probablement.<br>
'''πιθανώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''πιθανός''.<br>
'''πιθανώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''πιθανός''.<br>
'''πιθηκοειδής, -ής, -ές (adjectif)''' : .<br>
'''πίθηκος, -ήκου (nom commun) (m)''' : singe.<br>
'''πίθων, -ονος (nom commun) (m)''' : Singe, petit singe.<br>
'''πῖλος, -ίλου (nom commun) (m)''' : pileus.<br>
'''πίμπλημι (verbe)''' : remplir.<br>
'''πίναξ, -κος (nom commun) (m)''' : tableau.<br>
'''πίπτω (verbe)''' : tomber.<br>
'''πίσσα, -εως (nom commun) (f)''' : poix.<br>
'''πιστάκη, -ης (nom commun) (f)''' : pistachier.<br>
'''πιστάκιον, -ίου (nom commun) (n)''' : pistache.<br>
'''πίστις, -εως (nom commun) (f)''' : Foi. Confiance en autrui. Ce qui fait foi. Résultat de la confiance. Moyen d’inspirer confiance, de persuader ; preuve.<br>
'''πιστότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πιστός''.<br>
'''πιστότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πιστός''.<br>
'''πιστός, -ή, -όν (adjectif)''' : fidèle ; loyal.<br>
'''πιστότατα, -, - (adverbe)''' : Superlatif de ''πιστῶς''.<br>
'''πιστότερον, -, - (adverbe)''' : Comparatif de ''πιστῶς''.<br>
'''πιστῶς (adverbe)''' : fidèlement ; loyalement.<br>
'''πίστωσις, -ώσεως (nom commun) (f)''' : crédit.<br>
'''πισύγγιον, ίου (nom commun) (n)''' : Diminutif de ''πίσυγγος''.<br>
'''πίσυγγος, -ύγγου (nom commun) (m)''' : cordonnier.<br>
'''πίττα, -ας (nom commun) (f)''' : Forme attique de ''πίσσα''.<br>
'''πίτα, -ας (nom commun) (f)''' : pita.<br>
'''πλαγκτός, -ή, -όν (adjectif)''' : errant.<br>
'''πλαδαρός, -ά, -όν (adjectif)''' : Forme dorienne de ''βλαδαρός''.<br>
'''πλάζω (verbe)''' : errer.<br>
'''πλᾶθος, -άθους (nom commun) (n)''' : Forme dorienne de ''πλῆθος''.<br>
'''πλακοῦς, -ύντος (nom commun) (m)''' : Gâteau ; galette.<br>
'''πλανάτας, -ου (nom commun) (m)''' : Forme dorienne de ''πλανήτης''.<br>
'''πλάνης, -τος (nom commun) (m)''' : vagabond.<br>
'''πλανήτης, -ου (nom commun) (m)''' : planète.<br>
'''πλάν (préposition)''' : Forme dorienne de ''πλήν''.<br>
'''πλανῶ (verbe)''' : vagabonder.<br>
'''πλάξ, -κός (nom commun) (f)''' : plaque.<br>
'''πλάσμα, -τος (nom commun) (n)''' : Ouvrage façonné, modelé ; figure. Modulation de la voix. Contrefaçon, imitation. Travail de composition.<br>
'''πλάσσω (verbe)''' : former, mouler.<br>
'''πλατέως (adverbe)''' : largement.<br>
'''πλατύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''πλατύς''.<br>
'''πλατύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''πλατύς''.<br>
'''πλατύς, -εῖα, -ύ (adjectif)''' : large.<br>
'''πλάττω (verbe)''' : Forme attique de ''πλάσσω''.<br>
'''πλέθρον, -ου (nom commun) (n)''' : plèthre. (env. 30 m)<br>
'''πλεῖστος, -ίστη, -ῖστον (adjectif)''' : Superlatif de ''πολύς''.<br>
'''πλείων, -ων, -ῖον (adjectif)''' : Comparatif de ''πολύς''.<br>
'''πλεῖθος, -ίθους (nom commun) (n)''' : Forme béotienne de ''πλῆθος''.<br>
'''πλῆθος, -ήθους (nom commun) (n)''' : .<br>
'''πληθύς, -ος (nom commun) (n)''' : Forme ionienne de ''πλῆθος''.<br>
'''πληθώρα, -ας (nom commun) (f)''' : plénitude.<br>
'''πλήθω (verbe)''' : remplir.<br>
'''πληκτικός, -ή, -όν (adjectif)''' : ennuyeux.<br>
'''πληκτικότης, -ητος (nom commun) (f)''' : ennui.<br>
'''πλῆκτρον, -ήκτρου (nom commun) (n)''' : plectre.<br>
'''πλῆξις, -ήξεως (nom commun) (f)''' : ennui.<br>
'''πλήν (préposition)''' : plus que.<br>
'''πληρέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''πλήρης''.<br>
'''πληρέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''πλήρης''.<br>
'''πλήρης, -ης, -ῆρες (adjectif)''' : complet ; plein.<br>
'''πλήρως (adverbe)''' : complètement ; pleinement.<br>
'''πληρῶ (verbe)''' : compléter ; emplir.<br>
'''πλήσσω (verbe)''' : Frapper, battre. (Au passif) Être battu, subir une défaite.<br>
'''πλήττω (verbe)''' : Forme attique de ''πλήσσω''.<br>
'''πλοῖον, -ίου (nom commun) (n)''' : navire, bateau ; vaisseau.<br>
'''πλούσιος, -α, -ον (adjectif)''' : riche, opulent.<br>
'''πλουσιότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πλούσιος''.<br>
'''πλουσιότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πλούσιος''.<br>
'''πλουσίως (adverbe)''' : richement.<br>
'''πλουτηρός, -ά, -όν (adjectif)''' : dispensateur de richesse.<br>
'''πλουτίζω (verbe)''' : (voix active) enrichir ; (voix passive) s’enrichir, être enrichi.<br>
'''πλουτίνδην (adverbe)''' : en choisissant parmi les plus riches.<br>
'''πλουτοτήριος, -α, -ον (adjectif)''' : Capable d’enrichir.<br>
'''πλουτοκρατία, -ας (nom commun) (f)''' : ploutocratie.<br>
'''πλοῦτος, -ύτου (nom commun) (m)''' : richesse, opulence.<br>
'''πλουτῶ (verbe)''' : être riche.<br>
'''πλύνω (verbe)''' : laver des vêtements.<br>
'''πνεῦμα, -ύματος (nom commun) (n)''' : Souffle. Exhalaison ; odeur. Esprit.<br>
'''πνευματικός, -ή, -όν (adjectif)''' : spirituel.<br>
'''πνευματικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πνευματικός''.<br>
'''πνευματικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πνευματικός''.<br>
'''πνευματικότατα, -, - (adverbe)''' : Superlatif de ''πνευματικῶς''.<br>
'''πνευματικότερον, -, - (adverbe)''' : Comparatif de ''πνευματικῶς''.<br>
'''πνευματικῶς (adverbe)''' : spirituellement.<br>
'''πνεύμων, -ονος (nom commun) (m)''' : poumon.<br>
'''πνέω (verbe)''' : souffler.<br>
'''πνιγηρός, -ά, -όν (adjectif)''' : étouffant, où l’on étouffe (Par extension) Étroit, resserré.<br>
'''πνίγω (verbe)''' : étrangler, étouffer ; suffoquer.<br>
'''πνίξ, -γός (nom commun) (f)''' : crampe.<br>
'''ποδάριον -ίου (nom commun) (n)''' : Diminutif de ''πούς''.<br>
'''πόδιον, -ίου (nom commun) (n)''' : podium.<br>
'''πόθεν (adverbe interrogatif)''' : d’où.<br>
'''πόθος, -ου (nom commun) (m)''' : Désir ; regret.<br>
'''ποῖ (adverbe interrogatif)''' : où (avec mouvement).<br>
'''ποίησις, -ήσεως (nom commun) (f)''' : création.<br>
'''ποιμήν, -ένος (nom commun) (m)''' : berger.<br>
'''ποινή, -ῆς (nom commun) (f)''' : Expiation d’un crime. Argent par lequel on paye les parents de la victime, le prix du sang. Rançon, expiation, châtiment, vengeance. Délivrance. Compensation, récompense.<br>
'''ποῖος, -ία, -ῖον (adjectif interrogatif)''' : quel.<br>
'''ποιότης, -τος (nom commun) (f)''' : qualité.<br>
'''ποιῶ (verbe)''' : faire.<br>
'''πολεμικός, -ή, -όν (adjectif)''' : guerrier.<br>
'''πολεμικῶς (adverbe)''' : belliqueusement.<br>
'''πολεμικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''πολεμικός''.<br>
'''πολεμικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''πολεμικός''.<br>
'''πολεμιστής, -οῦ (nom commun) (m)''' : guerrier.<br>
'''πολεμίστρα, -ας (nom commun) (f)''' : guerrière.<br>
'''πόλεμος, -έμου (nom commun) (m)''' : guerre.<br>
'''πολεμῶ (verbe)''' : guerroyer.<br>
'''πολιός, -ός, -όν (adjectif)''' : gris.<br>
'''πόλις, -εως (nom commun) (f)''' : ville.<br>
'''πολιτεία, -ας (nom commun) (f)''' : citoyenneté.<br>
'''πολίτευμα, -τος (nom commun) (n)''' : gouvernement.<br>
'''πολιτεύω (verbe)''' : Être citoyen. Participer au gouvernement, faire de la politique. (Au passif) Être gouverné, en parlant de l’État.<br>
'''πολίτης, -ου (nom commun) (m)''' : citoyen.<br>
'''πολιτικός, -ή, -όν (adjectif)''' : civique.<br>
'''πολῖτις, -ίτιδος (nom commun) (f)''' : citoyenne.<br>
'''πολλάκις (adverbe)''' : souvent.<br>
'''πολλῶς (adverbe)''' : .<br>
'''πολυμάθεια, -ας (nom commun) (f)''' : érudition.<br>
'''πολυμαθής, -ής, -ές (adjectif)''' : érudit.<br>
'''πολυμήτις, -, - (adjectif)''' : .<br>
'''πολυμήχανος, -η, -ον (adjectif)''' : ingénieux.<br>
'''πολύτροπος, -ος, -ον (adjectif)''' : astucieux.<br>
'''πολύς, -λλή, -ύ (adjectif)''' : nombreux.<br>
'''πολύχρωμος, -η, -ον (adjectif)''' : multicolore.<br>
'''πομφολυγοπάφλασμα, -άσματος (nom commun) (n)''' : .<br>
'''πομφόλυξ, -γος (nom commun) (m)''' : bulle ; ornement en forme de bulle, de rond. (Chimie) Oxyde de zinc.<br>
'''πομφός, -οῦ (nom commun) (m)''' : verrue.<br>
'''πονηρία, -ας (nom commun) (f)''' : ruse.<br>
'''πονηρός, -ά, -όν (adjectif)''' : mauvais, méchant ; pervers.<br>
'''πονηρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πονηρός''.<br>
'''πονηρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πονηρός''.<br>
'''πονηρῶς (adjectif)''' : mauvaisement, méchamment ; perversement.<br>
'''πόποι (interjection)''' : hélas.<br>
'''πορδή, -ῆς (nom commun) (f)''' : pet. (gaz intestinal)<br>
'''πορθμεῖον, -ίου (nom commun) (n)''' .<br>
'''πορθμεύς, -έως (nom commun) (m)''' nocher.<br>
'''πορθμεύω (verbe)''' .<br>
'''πορθμός, -οῦ (nom commun) (m)''' : détroit.<br>
'''πορνεία, -ας (nom commun)''' : Fornication, prostitution.<br>
'''πορνεῖον, -ίου (nom commun) (n)''' lupanar.<br>
'''πόρνευμα, -ύµατος (nom commun (n)''' : .<br>
'''πόρνευσις, -ύσεως (nom commun) (f)''' : .<br>
'''πορνεύτρια, -ας (nom commun) (f)''' : .<br>
'''πορνεύω (verbe)''' : prostituer.<br>
'''πόρνη, -ης (nom commun) (f)''' : Prostituée, putain.<br>
'''πορνικός, -ή, -όν (adjectif)''' : de lupanar.<br>
'''πορνοϐοσκεῖον, -ίου (nom commun) (n)''' : lupanar.<br>
'''πορνοϐοσκία, -ας (nom commun) (f)''' : prostitution.<br>
'''πορνοϐοσκός, -οῦ (nom commun) (m)''' : tenancier.<br>
'''πορνοϐοσκῶ (verbe)''' : gérer un lupanar.<br>
'''πορνοδιάκονος, -όνου (nom commun) (m/f)''' : .<br>
'''πορνοδιδάσκαλος, -άλου (nom commun) (m) ''' : .<br>
'''πορνοδύτης, -ου (nom commun) (m)''' .<br>
'''πορνοφίλης, -ου (nom commun) (m)''' : amateur de prostituées.<br>
'''πορνογενής, -ής, -ές (adjectif)''' : .<br>
'''πορνογέννητος, -ος, -ον (adjectif)''' : né dans un lupanar.<br>
'''πορνογράφος, -ου (nom commun) (m)''' : pornographe.<br>
'''πορνοκοπέω (verbe)''' : .<br>
'''πορνοκοπία, -ας (nom commun) (f)''' .<br>
'''πορνοκόπος, -ου (nom commun) (m)''' : client de lupanar.<br>
'''πορνομανής, -ής, -ές''' : furieux contre la prostitution.<br>
'''πόρνος, -ου (nom commun) (m)''' : Prostitué ; gigolo.<br>
'''πόρπη, -ης (nom commun) (f)''' : broche (bijou).<br>
'''πόρ, -δός (nom commun) (m)''' : Forme laconienne de ''πούς''.<br>
'''πόρρω (adverbe)''' : Forme attique de ''πρόσω''.<br>
'''πορφύρεος, -α, -ον (adjectif)''' : (chez Homère) Sombre. (grec post-homérique) Pourpre.<br>
'''πορφύρω (verbe)''' : Bouillir, fulminer ; rougir.<br>
'''πόσις, -εως (nom commun) (f)''' : action de boire.<br>
'''πόσις, -ιος (nom commun) (m)''' : époux ; mari.<br>
'''πόσσις, -ιος (nom commun) (m)''' : Forme poétique de ''πόσις''. (au sens d’« époux », « mari »)<br>
'''πόσος, -η, -ον (adjectif interrogatif)''' : combien.<br>
'''ποσότης, -τος (nom commun) (f)''' : quantité.<br>
'''πός, -δός (nom commun) (m)''' : Forme dorienne de ''πούς''.<br>
'''ποταμός, -οῦ (nom commun) (m)''' : fleuve.<br>
'''πότε (adverbe interrogatif)''' : quand.<br>
'''ποτέ (adverbe)''' (Devient ''ποτ᾽'' devant un mot commençant par une voyelle à esprit doux, et ''ποθ᾽'' devant un mot commençant par une voyelle à esprit rude.) : jamais.<br>
'''ποτήριον, -ίου (nom commun) (n)''' : coupe. (récipient)<br>
'''ποτήρ, -ῆρος (nom commun) (m)''' : verre. (récipient)<br>
'''πότμος, -ου (nom commun) (m)''' : destin ; sort.<br>
'''πότνια, -ίης (nom commun) (f)''' : épouse ; maîtresse.<br>
'''πούς, -δός (nom commun) (m)''' : pied.<br>
'''ποῦ (adverbe interrogatif)''' : où (sans mouvement) ; comment.<br>
'''πρᾶγμα, -άγματος (nom commun) (n)''' : Chose, évènement ; affaire. Fait.<br>
'''πραγματικός, -ή, -όν (adjectif)''' : évènementiel ; factuel.<br>
'''πραγματικῶς (adverbe)''' : évènementiellement ; factuellement.<br>
'''πραγματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''πραγματικός''.<br>
'''πραγματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''πραγματικός''.<br>
'''πραικόκιον, -ίου (nom commun) (n)''' : abricot.<br>
'''πρακτόρειον, -ίου (nom commun) (n)''' : agence.<br>
'''πράκτωρ, -ορος (nom commun) (m)''' : agent.<br>
'''πρᾶξις, -άξεως (nom commun) (f)''' : action.<br>
'''πραπίς, -δος (nom commun) (f)''' : intelligence.<br>
'''πράσινος, -ίνη, -άσινον (adjectif)''' : vert poireau.<br>
'''πράσσω (verbe)''' : faire, pratiquer.<br>
'''πρατήρ, -ῆρος (nom commun) (m)''' : vendeur.<br>
'''πρᾶτος, -άτη, -ᾶτον (adjectif)''' : Forme dorienne de ''πρῶτος''.<br>
'''πράττω (verbe)''' : Forme attique de ''πράσσω''.<br>
'''πρέπω (verbe)''' : .<br>
'''πρέσϐυς, -έως (nom commun) (m)''' : Envoyé ; député ; ambassadeur.<br>
'''πρεσϐυτέριον, -ίου (nom commun) (n)''' : assemblée des anciens.<br>
'''πρῆγμα, -ήγματος (nom commun) (f)''' : Forme ionienne de ''πρᾶγμα''.<br>
'''πρηκτήρ, -ός (nom commun) (m)''' : Forme homérique et ionienne de ''πράκτωρ''.<br>
'''πρῆξις, -ήξεως (nom commun) (f)''' : Forme homérique et ionienne de ''πρᾶξις''.<br>
'''πρήσσω (verbe)''' : Forme homérique et ionienne de ''πράσσω''.<br>
'''πρῆχμα, -ήχματος (nom commun) (f)''' : Autre forme ionienne de ''πρᾶγμα''.<br>
'''πρῖγκιψ, -ίγκιπος (nom commun) (m)''' : prince. (chef d’une principauté)<br>
'''πριγκιπίσσα, -ας (nom commun) (f)''' : princesse. (cheffe d’une principauté)<br>
'''πρίν (conjonction)''' : avant que.<br>
'''πριόνιον, -ίου (nom commun) (n)''' : diminutif de ''πρίων''.<br>
'''πρίων, -ονος (nom commun) (m)''' : scie.<br>
'''προάστειος, -, - (adjectif)''' : suburbain.<br>
'''προάστειον, -ίου (nom commun) (n)''' : banlieue.<br>
'''προϐαίνω (verbe)''' : marcher en avant.<br>
'''προϐατίνα, -ας (nom commun) (f)''' : brebis.<br>
'''πρόϐατον, -άτου (nom commun) (n)''' : mouton.<br>
'''πρόϐλημα, -ήματος (nom commun) (n)''' : problème.<br>
'''πρόγνωσις, -ώσεως (nom commun) (f)''' : .<br>
'''πρόγονος, -όνου (nom commun) (m)''' : ancêtre.<br>
'''προπάτωρ, -ορος (nom commun) (m)''' : ancêtre.<br>
'''πρόγραμμα, -άμματος (nom commun) (n)''' : notice écrite publique.<br>
'''προγράφω (verbe)''' : écrire une notice publique.<br>
'''προδίδωμι (verbe)''' : trahir.<br>
'''προδότης, -ου (nom commun) (m)''' : traître.<br>
'''προδοτικός, -ή, -όν (adjectif)''' : traître.<br>
'''προδοσία, -ας (nom commun) (f)''' : trahison.<br>
'''προοίμιον, -ίου (nom commun) (n)''' : prélude.<br>
'''προκινῶ (verbe)''' : .<br>
'''πρόκοιτος, -ίτου (nom commun) (m)''' : chambellan.<br>
'''προλαμϐάνω (verbe)''' : prévoir.<br>
'''πρόληψις, -ήψεως (nom commun) (f)''' : prévention.<br>
'''πρόλογος, -όγου (nom commun) (m)''' : préface.<br>
'''προσϐάλλω (verbe)''' : .<br>
'''προσϐολή, -ῆς (nom commun) (f)''' : .<br>
'''προσευχή, -ῆς (nom commun) (f)''' : prière.<br>
'''προσεύχομαι (verbe)''' : Adresser une prière. (Absolu) Adorer, prier, supplier.<br>
'''προσήλωσις, -ώσεως (nom commun) (f)''' : attachemeent.<br>
'''πρόσθεν (adverbe)''' Devant ; en avant.<br>
'''προσιτός, -ή, -όν (adjectif)''' : abordable.<br>
'''πρόσειμι (verbe)''' : aborder.<br>
'''προσθιγγάνω (verbe)''' : .<br>
'''προσκέφαλον, -άλου (nom commun) (m)''' : oreiller.<br>
'''πρόσκοπος, -όπου (nom commun) (m)''' : éclaireur.<br>
'''πρόσκοπευω (verbe)''' : .<br>
'''προσποιῶ (verbe)''' : .<br>
'''προσωποποιία, -ας (nom commun) (f)''' : personnification.<br>
'''πρόσω (adverbe)''' : .<br>
'''πρός (adverbe ; préposition)''' : Auprès, à côté ; en outre.<br>
'''πρότατος, -άτα, -άτετον (adjectif)''' : Superlatif de ''πρῶτος''.<br>
'''πρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πρῶτος''.<br>
'''προτί (adverbe ; préposition)''' : Forme homérique de ''πρός''.<br>
'''ποτί (adverbe ; préposition)''' : Forme homérique et dorienne de ''πρός''.<br>
'''ποί (adverbe ; préposition)''' : Forme dorienne de ''πρός''.<br>
'''πορτί (adverbe ; préposition)''' : Forme crétoise de ''πρός''.<br>
'''πός (adverbe ; préposition)''' : Forme arcado-chypriote de ''πρός''.<br>
'''πρές (adverbe ; préposition)''' : Forme éolienne de ''πρός''.<br>
'''πέρτι (adverbe ; préposition)''' : Forme pamphylienne de ''πρός''.<br>
'''πρᾶος, -ος, -ᾷον (adjectif)''' : doux ; calmant.<br>
'''πραότης, -τος (nom commun) (f)''' : Douceur, bonté ; facilité de caractère.<br>
'''πραύς, -εῖα, -ύ (adjectif)''' : Variante de ''πρᾶος''.<br>
'''πραΰς, -εΐα, -ΰ (adjectif)''' : Forme poétique de ''πρᾶος''.<br>
'''πρήξιμον, -ίματος (nom commun) (n)''' : Enflure, gonflement ; ballonnement, boursouflure, fluxion.<br>
'''πρῖγκιψ, -ίγκιπος (nom commun) (m)''' : prince (chef d’une principauté).<br>
'''πριγκίπισσα, -ας (nom commun) (f)''' : princesse (cheffe d’une principauté).<br>
'''πρό (adverbe ; préposition)''' devant.<br>
'''προ- (préfixe)''' pré-.<br>
'''παράρτημα, -ήματος (nom commun) (n)''' : annexe.<br>
'''προζύμιον, -ίου (nom commun) (n)''' : .<br>
'''προζυμίτης, -ου (nom commun) (m)''' : .<br>
'''πρόσθεσις, -έσεως (nom commun) (f)''' : addition.<br>
'''προσφορά, -ᾶς (nom commun) (f)''' : offre.<br>
'''προσωπεῖον, -ίου (nom commun) (n)''' : masque.<br>
'''προσωπίς, -δος (nom commun) (f)''' : masque.<br>
'''προσ- (préfixe)''' : Vers, à ; Auprès. En outre, encore. Excessivement ; tout à fait.<br>
'''προσαρτῶ (verbe)''' : annexer.<br>
'''προστίθημι (verbe)''' : additionner ; ajouter.<br>
'''προσομοίωσις, -ώσεως (nom commun) (f)''' : simulation.<br>
'''προσποιῶ (verbe)''' : simuler.<br>
'''πρότανις, -άνεως (nom commun) (m)''' : Forme éolienne de ''πρύτανις''.<br>
'''προῦμνον, -ύμνου (nom commun) (n)''' : prune.<br>
'''προφατεία, -ας (nom commun) (f)''' : Forme dorienne de ''προφητεία''.<br>
'''προφάτης, -ου (nom commun) (m)''' : Forme dorienne de ''προφήτης''.<br>
'''προφητεία, -ας (nom commun) (f)''' : prophétie.<br>
'''προφήτης, -ου (nom commun) (m)''' : prophète.<br>
'''προφυλακτικός, -ή, -όν (adjectif)''' : .<br>
'''προφυλάξις, -εως (nom commun) (f)''' : .<br>
'''προφυλάσσω (verbe)''' : .<br>
'''πρύμνα, -ας (nom commun) (f)''' : poupe.<br>
'''πρύμνη, -ης (nom commun) (f)''' : Forme ionienne de ''πρύμνα''.<br>
'''πρυτανεῖον, -ίου (nom commun) (n)''' : (À Athènes) mairie.<br>
'''πρύτανις, -άνεως (nom commun) (m)''' : prytane.<br>
'''πρωΐ (adverbe)''' : tôt.<br>
'''πρῴ (adverbe)''' : Forme attique de ''πρωΐ''.<br>
'''πρωκτός, -οῦ (nom commun) (m)''' : anus.<br>
'''πρωκτόσοφος, -όφου (nom commun) (m/f)''' : Expert en débauche contre nature.<br>
'''πρῷρα, -ῴρας (nom commun) (f)''' : proue.<br>
'''πρωταγωνιστής, -οῦ (nom commun) (m)''' : protagoniste.<br>
'''πρῶτος, -ώτη, -ῶτον (adjectif)''' : premier.<br>
'''πρωτόκολλον, -όλλου (nom commun) (n)''' : frontispice.<br>
'''πτελέα, -ας (nom commun) (f)''' : orme.<br>
'''πτέρινος, -ίνη, -ινον (adjectif)''' : .<br>
'''πτέρις, -δος (nom commun) (f)''' : fougère.<br>
'''πτερόεις, -εσσα, -εν (adjectif)''' : ailé.<br>
'''πτερόν, -οῦ (nom commun) (n)''' : aile.<br>
'''πτέρνα, -ης (nom commun) (f)''' : talon. (partie postérieure du pied)<br>
'''πτέρυξ, -υγος (nom commun) (f)''' : Aile ; nageoire.<br>
'''πτηνόν, -οῦ (nom commun) (n)''' : oiseau.<br>
'''πτηνοπέδιλος, -ίλου (nom commun) (m)''' : talaria.<br>
'''πτισάνη, -ης (nom commun) (f)''' : tisane.<br>
'''πτίσσω (verbe)''' : .<br>
'''πτίττω (verbe)''' : Forme attique de ''πτίσσω''.<br>
'''πτόλεμος, -έμου (nom commun) (m)''' : Forme homérique de ''πόλεμος''.<br>
'''πτολίεθρον, -έθρου (nom commun) (n)''' : citadelle.<br>
'''πτόλις, -εως (nom commun) (f)''' : Forme homérique de ''πόλις''.<br>
'''πτύον, -ου (nom commun) (n)''' : pelle.<br>
'''πτύω (verbe)''' : cracher.<br>
'''πτῶσις, -ώσεως (nom commun) (f)''' : chute.<br>
'''πτώσσω (verbe)''' : chuter.<br>
'''πτωχεία, -ας (nom commun) (f)''' : pauvre.<br>
'''πτωχεῖον, -ίου (nom commun) (n)''' : hospice.<br>
'''πτώχευσις, -ύσεως (nom commun) (f)''' : banqueroute.<br>
'''πτωχεύω (verbe)''' : faire banqueroute.<br>
'''πτωχός, -ή, -όν (adjectif)''' : pauvre.<br>
'''πτωχοτροφεῖον, -ίου (nom commun) (n)''' : hospice.<br>
'''πτωχοτρόφος, -ου (nom commun) (m)''' : ptôchotrophe.<br>
'''πτωχότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πτωχός''.<br>
'''πτωχότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πτωχός''.<br>
'''πτωχῶς (adverbe)''' : pauvrement.<br>
'''πυγή, -ῆς (nom commun) (f)''' : fesse.<br>
'''πυγηφαvοῦς, -οῦ (nom commun) (m)''' : mooning. (Action de montrer ses fesses nues en baissant son pantalon et en se penchant en avant.)<br>
'''πυγίδιον, -ίου (nom commun) (n)''' : croupion.<br>
'''πυγίζω (verbe)''' : sodomiser.<br>
'''πυγμαῖος, -ία -ῖον (adjectif)''' : pygmée.<br>
'''πυγμή, -ῆς (nom commun) (f)''' : poing.<br>
'''πύελος, -έλου (nom commun) (f)''' : bassin (os).<br>
'''πυθμήν, -ένος (nom commun) (m)''' : fond.<br>
'''πυκνός, -ή, -όν (adjectif)''' : dense, sagace.<br>
'''πυκνότατα, -, - (adverbe)''' : Superlatif de ''πυκνῶς''.<br>
'''πυκνότερον, -, - (adverbe)''' : Comparatif de ''πυκνῶς''.<br>
'''πυκνότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πυκνός''.<br>
'''πυκνότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πυκνός''.<br>
'''πυκνότης, -τος (nom commun) (f)''' : densité ; sagacité.<br>
'''πυκνῶς (adverbe)''' : densément ; sagacement.<br>
'''πύλη, -ης (nom commun) (f)''' : (Au singulier) battant de porte. (Au pluriel) porte.<br>
'''πυλωρός, -οῦ (nom commun) (m)''' : Portier. (Anatomie) pylore.<br>
'''πύνδαξ, -κος (nom commun) (m)''' : fond.<br>
'''πυνθάνομαι (verbe)''' : chercher à savoir ; s’enquérir ; s’informer de ; apprendre en s’informant ; apprendre<br>
'''πυξίς, -δος (nom commun) (f)''' : pyxide.<br>
'''πύον, -ου (nom commun) (n)''' : pus.<br>
'''πυραμίς, -δος (nom commun) (f)''' : pyramide.<br>
'''πυργίτης, -ου (nom commun) (m)''' : moineau.<br>
'''πύργος, -ου (nom commun) (m)''' : Tour. (Par extension) Enceinte garnie de tours. (Par extension) Citadelle, remparts. (Par analogie) Sorte de bataillon carré.<br>
'''πύρινος, -η, -ον (adjectif)''' : enflammé.<br>
'''πυρινότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πύρινος''.<br>
'''πυρινότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πύρινος''.<br>
'''πύρινως (adverbe)''' : .<br>
'''πῦρ, -ός (nom commun) (n)''' : feu.<br>
'''πυρρός, -ά, -όν (adjectif)''' : .<br>
'''πυρρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''πυρρός''.<br>
'''πυρρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''πυρρός''.<br>
'''πυρρῶς (adverbe)''' : .<br>
'''πύσμα, -τος (nom commun) (n)''' : question ouverte.<br>
'''πύστις, -εως (nom commun) (f)''' : Enquête, question. Apprentissage, chose apprise.<br>
'''πυστός, -ή, -όν (adjectif)''' : .<br>
'''πωγώνιον, -ίου (nom commun) (m)''' : menton.<br>
'''πώγων, -ονος (nom commun) (m)''' : Barbe ; barbe d’animal, de plante. Barbelure. Langue de feu.<br>
'''πωλέω (verbe)''' : vendre.<br>
'''πώλης, -ου (nom commun) (m)''' : vendeur.<br>
'''πωλητής, -οῦ (nom commun) (m)''' : vendeur.<br>
'''πῶμα, -ώματος (nom commun) (n)''' : bouchon.<br>
'''πῶυ, -ώεος (nom commun) (n)''' : Troupeau d’ovins ou de chèvres.<br>
'''πῶς (adverbe)''' : comment.<br>
'''Παιάν, -ᾶνος (nom commun) (m)''' : péan. (nom propre) : Péan.<br>
'''Πακτωλός, -οῦ (nom propre) (m)''' : Pactole.<br>
'''Παλαιὰ Διαθήκη (locution nominale) (f)''' : Ancien Testament.<br>
'''Παλλάδιον, -ίου (nom propre) (n)''' : Palladium.<br>
'''Πάλλας, -αντος (nom propre) (m)''' : Pallas (nom masculin).<br>
'''Παλλάς, -δος (nom propre) (f)''' : Pallas (nom féminin).<br>
'''Πάλμυρα, -ύρας (nom propre) (f)''' : Palmyre.<br>
'''Πάμφιλος, -ίλου (nom propre) (m)''' : Pamphile.<br>
'''Πανάκεια, -ας (nom propre) (f)''' : [[wikt:Panacée|Panacée]].<br>
'''Πανδώρα, -ας (nom propre) (f)''' : [[wikt:Pandore|Pandore]].<br>
'''Πάν, -ός (nom propre) (m)''' : [[wikt:Pan|Pan]].<br>
'''Πανοῦργος, -ύργου (nom propre) (m)''' : Panurge.<br>
'''Πάνθειον, -ίου (nom propre) (m)''' : Panthéon.<br>
'''Παρθένος, -ου (nom propre) (f)''' : Vierge.<br>
'''Παρθενών, -ῶνος (nom commun) (m)''' : Parthénon.<br>
'''Παρθία, -ας (nom propre) (f)''' : Parthie.<br>
'''Πάρθος, -ου (nom commun) (m)''' : Parthe.<br>
'''Πάρις, -δος (nom propre) (m)''' : Pâris.<br>
'''Παρμενίδης, -ου (nom propre) (m)''' : Parménide.<br>
'''Πασιφάη, -ης (nom propre) (f)''' : Pasiphaé.<br>
'''Παῦλος, -ύλου (nom propre) (m)''' : Paul.<br>
'''Παῦνι (nom propre) (m)''' : Payni.<br>
'''Παυσανίας, -ου (nom propre) (m)''' : Pausanias.<br>
'''Παχώμιος, -ίου (nom propre) (m)''' : Pacôme.<br>
'''Παχών (nom propre) (m)''' : Pachon.<br>
'''Πελοπόννησος, -ήσου (nom propre) (f)''' : Péloponnèse.<br>
'''Πέλοψ, -πος (nom propre) (m)''' : Pélops.<br>
'''Πεμφρηδώ, -οῦς (nom propre) (f)''' : Pemphrédo (une des Grées).<br>
'''Πεντάτευχος, -ύχου (nom propre) (f)''' : Pentateuque.<br>
'''Πέργαμον, -άμου (nom propre) (n)''' : Pergame.<br>
'''Περίϐοια, -ίας (nom propre) (f)''' : Périboée.<br>
'''Περικλῆς, -έους (nom propre) (m)''' : Périclès.<br>
'''Περίλεως, -εω (nom propre) (m)''' : Périléos.<br>
'''Περσέπολις, -όλεως (nom propre) (f)''' : Persépolis.<br>
'''Περσεπολίτης, -ου (nom commun) (m)''' : Persépolitain.<br>
'''Πέρσης, -ου (nom commun) (m)''' : Perse.<br>
'''Περσίς, -δος (nom propre) (f)''' : Perse.<br>
'''Περσίς, -δος (nom commun) (f)''' : Perse.<br>
'''Πετεφρής, -οῦ (nom propre) (m)''' : Potiphar.<br>
'''Πήγασος, -άσου (nom propre) (m)''' : Pégase.<br>
'''Πηλεύς, -έως (nom propre) (m)''' : Pélée.<br>
'''Πηνειός, -οῦ (nom propre) (m)''' : Pénée.<br>
'''Πηνελόπη, -ης (nom propre) (f)''' : Pénélope.<br>
'''Πίστις, -εως (nom propre) (f)''' : Pistis.<br>
'''Πιτθεύς, -έως (nom propre) (m)''' : Pitthée. (Grand-père de Thésée.)<br>
'''Πλάτων, -ωνος (nom propre) (m)''' : Platon.<br>
'''Πλευρών, -ῶνος (nom propre) (m/f)''' : Pleuron.<br>
'''Πλούταρχος, -άρχου (nom propre) (m)''' : Plutarque.<br>
'''Πλουτεύς, -έως (nom propre) (m)''' : Autre nom de Pluton.<br>
'''Πλουτίς, -δος (nom propre) (f)''' : Ploutis.<br>
'''Πλοῦτος, -ύτου (nom propre) (m)''' : [[wikt:Ploutos|Ploutos]].<br>
'''Πλούτων, -ωνος (nom propre) (m)''' : [[wikt:Pluton|Pluton]].<br>
'''Πόθος, - (nom propre) (m)''' : [[wikt:Pothos|Pothos]].<br>
'''Ποικίλη, -ης (nom propre) (f)''' : Pœcile. (portique au nord de l’Agora d’Athènes.)<br>
'''Πολύϐιος, -ίου (nom propre) (m)''' : Polybe.<br>
'''Πολυμνία, -ας (nom propre) (f)''' : Polymnie.<br>
'''Πολύαινος, -ίνου (nom propre) (m)''' : Polyen.<br>
'''Πολυνείκης, -ους (nom propre) (m)''' : Polynice.<br>
'''Πολυπήμων, -ονος (nom propre) (m)''' : Polypémon (véritable nom de Procuste).<br>
'''Πολύφημος, -ήμου (nom propre) (m)''' : Polyphème.<br>
'''Πομπηία, -ας (nom propre) (f)''' : Pompéi.<br>
'''Πομπήϊος, -ΐου (nom propre) (m)''' : Pompée.<br>
'''Ποσειδώνιος, - (nom propre) (m)''' : Posidonios (Philosophe grec stoïcien né à Apamée en 135 av. J.-C. et mort à Rome en 51 av. J.-C. .).<br>
'''Ποσειδάν, - (nom propre) (m)''' : Forme dorienne de ''Ποσειδῶν''.<br>
'''Ποσειδάων, - (nom propre) (m)''' : Forme homérique de ''Ποσειδῶν''.<br>
'''Ποσειδέων, - (nom propre) (m)''' : Forme ionienne de ''Ποσειδῶν''.<br>
'''Ποσειδῶν, -ῶνος (nom propre) (m)''' : [[wikt:Poséidon|Poséidon]].<br>
'''Ποτειδάν, - (nom propre) (m)''' : Autre forme dorienne de ''Ποσειδῶν''.<br>
'''Ποτείδαν, - (nom propre) (m)''' : Forme éolienne de ''Ποσειδῶν''.<br>
'''Ποτειδᾶς, - (nom propre) (m)''' : Forme de ''Ποσειδῶν''.<br>
'''Ποτειδάων, - (nom propre) (m)''' : Forme crétoise et béotienne de ''Ποσειδῶν''.<br>
'''Πραξιτέλης, -ους (nom propre) (m)''' : Praxitèle.<br>
'''Πρίαμος, -άμου (nom propre) (m)''' : Priam.<br>
'''Πρόκνη, -ης (nom propre) (f)''' : Procné.<br>
'''Προκρούστης, -ου (nom propre) (m)''' : Procuste (surnom de Polypémon.).<br>
'''Προκύων, -ός (nom propre) (m)''' : Procyon.<br>
'''Προμηθεύς, -έως (nom propre) (m)''' : Prométhée.<br>
'''Προξένος, -ρου (nom propre) (m)''' : Proxenos.<br>
'''Προυσίας, -ου (nom propre) (m)''' : Prusias.<br>
'''Πρωταγόρας, -ου (nom propre) (m)''' : Protagoras.<br>
'''Πυγμαλίων, -ωνος (nom propre) (m)''' : Pygamlion.<br>
'''Πυθαγόρας, -ου (nom propre) (m)''' : Pythagore.<br>
'''Πυθόπολις, -όλεως (nom propre) (f)''' : Antioche du Méandre.<br>
'''Πύθων, -ωνος (nom propre) (m)''' : Python.<br>
'''Πυθώ, -οῦς (nom propre) (f)''' : Ancien nom de Delphes.<br>
'''Πυλάδας, -ου (nom propre) (m)''' : Forme dorienne de ''Πυλάδης''.<br>
'''Πυλάδης, -ου (nom propre) (m)''' : Pylade.<br>
'''Πύραμος, -άμου (nom propre) (m)''' : Pyrame.<br>
'''Πυρετός, -οῦ (nom propre) (m)''' : (fleuve d’Europe de l’Est).<br>
'''Πυργοπολυνείκης, -ους (nom propre) (m)''' : Pyrgopolinice.<br>
'''Πύργος, -ου (nom propre) (m)''' : Pyrgos.<br>
'''Πύρρα, -ας (nom propre) (f)''' : Pyrrha.<br>
'''Πύρρος, -ου (nom propre) (m)''' : Pyrrhus.<br>
'''Πύρρων, -ωνος (nom propre) (m)''' : Pyrrhon.<br>
'''Πῶλος, -ώλου (nom propre) (m)''' : Polos.<br>
==Ρ==
'''ῥᾶ (nom commun) (n)''' : rhubarbe.<br>
'''ῥαϐϐί (nom commun) (m)''' : rabbin.<br>
'''ῥαϐδομαντεία, -ας (nom commun) (f)''' : Divination pratiquée grâce à une baguette.<br>
'''ῥαϐδονόμος, -ου (nom commun) (m)''' : licteur.<br>
'''ῥαϐδοῦχος, -ύχου (nom commun) (m)''' : licteur.<br>
'''ῥάϐδος, -ου (nom commun) (f)''' : baguette.<br>
'''ῥαγάς, -δος (nom commun) (f)''' : .<br>
'''ῥαιϐηδόν, -οῦ (nom commun) (n)''' .<br>
'''ῥαιϐοειδής, -ής, -ές (adjectif) ''' .<br>
'''ῥαιϐόκρανος, -άνου (nom commun) (m)''' .<br>
'''ῥαιϐοσκελής, -ής, -ές (adjectif) ''' .<br>
'''ῥαιϐός, -ή, -όν (adjectif)''' : tendu.<br>
'''ῥαιϐότης, -τος (nom commun) (f)''' .<br>
'''ῥαιϐῶς (adverbe)''' : de façon tendue.<br>
'''ῥαιϐῶ (verbe)''' tendre.<br>
'''ῥαδινός, -ή -όν (adjectif)''' : souple.<br>
'''ῥᾴδιος, -α, -ον (adjectif)''' : facile.<br>
'''ῥᾳδίως (adverbe)''' : facilement.<br>
'''ῥᾷστος, -, - (adjectif)''' : Superlatif de ''ῥᾴδιος''.<br>
'''ῥᾴων, -, - (adjectif)''' : Comparatif de ''ῥᾴδιος''.<br>
'''ῥάμνος, -ου (nom commun) (f)''' nerprun épineux.<br>
'''ῥάμφος, -ους (nom commun) (n)''' : bec (d’un oiseau).<br>
'''ῥάξ, -γός (nom commun) (m)''' : téton.<br>
'''ῥάπισμα, -ίσματος (nom commun) (n)''' : gifle.<br>
'''ῥαπίς, -δος (nom commun) (f)''' : verge ; bâton.<br>
'''ῥάπτης, -ου (nom commun) (n)''' : tailleur (de vêtements).<br>
'''ῥαπτός, -ή, -όν (adjectif)''' : cousu.<br>
'''ῥάπτω (verbe)''' : coudre.<br>
'''ῥάπυς, -ος (f)''' : navet.<br>
'''ῥάσσω (verbe)''' : .<br>
'''ῥάττω (verbe)''' : Forme attique de ''ῥάσσω''.<br>
'''ῥαφανιδῶ (verbe)''' : .<br>
'''ῥαφανίδωσις, -ώσεως (nom commun) (f)''' : .<br>
'''ῥαφανός, -οῦ (nom commun) (m)''' : radis.<br>
'''ῥαφεύς, -έως (nom commun) (m)''' : couturier.<br>
'''ῥαφή, -ῆς (nom commun) (f)''' : couture.<br>
'''ῥαφίς, -δος (nom commun) (f)''' : Aiguille, poinçon. Aiguille (poisson de mer).<br>
'''ῥάφυς, -ος (nom commun) (f)''' : Variante de ''ῥάπυς''.<br>
'''ῥαχίζω (verbe)''' : .<br>
'''ῥάχις, -εως (nom commun) (f)''' : épine dorsale.<br>
'''ῥαχιστής, -οῦ (nom commun) (m)''' : .<br>
'''ῥαχιστός, -ός, -όν (adjectif)''' : .<br>
'''ῥαχίτης, -ης, -ες, (adjectif)''' : .<br>
'''ῥαχός, -ή, -όν, (adjectif)''' : superficiel.<br>
'''ῥαχός, -οῦ (nom commun) (m)''' : .<br>
'''ῥάψις, -εως (nom commun) (f)''' : .<br>
'''ῥαψῳδία, -ας (nom commun) (f)''' : récitation d’un poème épique.<br>
'''ῥαψῳδός, -οῦ (nom commun) (m)''' : barde.<br>
'''ῥέδδω (verbe)''' : Forme béotienne et dorienne de ''ῥέζω''.<br>
'''ῥέζω (verbe)''' : Travailler, faire, exécuter.<br>
'''ῥέπω (verbe)''' : incliner.<br>
'''ῥεῦμα, -ύματος (nom commun) (n)''' : rhume.<br>
'''ῥευματίζομαι (verbe)''' : souffrir de rhumatismes.<br>
'''ῥευματισμός, -οῦ (nom commun) (m)''' : rhumatisme.<br>
'''ῥευστός, -ή -όν (adjectif)''' : liquide.<br>
'''ῥέω (verbe)''' : couler.<br>
'''ῥῆγμα, -ήγματος (nom commun) (n)''' : .<br>
'''ῥήγνυμι (verbe)''' : .<br>
'''ῥῆμα, -ήματος (nom commun) (n)''' : verbe.<br>
'''ῥῆξις, -ήξεως (nom commun) (f)''' : déchirure.<br>
'''ῥῆον, -ήου (nom commun) (n)''' : rhubarbe.<br>
'''ῥῆσις, -ήσεως (nom commun) (f)''' : citation.<br>
'''ῥήσσω (verbe)''' : Forme ionienne de ''ῥάσσω''.<br>
'''ῥητορικός, -ή, -όν (adjectif)''' : oratoire.<br>
'''ῥητορικῶς (adverbe)''' : oratoirement.<br>
'''ῥητορικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ῥητορικός''.<br>
'''ῥητορικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ῥητορικός''.<br>
'''ῥητορικώτατα, -, - (adverbe)''' : Superlatif de ''-ικῶς''.<br>
'''ῥητορικώτερον, -, - (adverbe)''' : Comparatif de ''-ικῶς''.<br>
'''ῥητός, -ή, -όν (adjectif)''' : Dit ; dicible, (Mathématiques) rationnel.<br>
'''ῥήτρα, -ας (nom commun) (f)''' : clause.<br>
'''ῥήτωρ, -ορος (nom commun) (m/f)''' : orateur.<br>
'''ῥηχός, -ή -όν (adjectif)''' : Forme ionienne de ''ῥαχός''.<br>
'''ῥῖγος, -ίγους (nom commun) (n)''' : frisson.<br>
'''ῥιγῶ (verbe)''' : frissonner.<br>
'''ῥίζα, -ης (nom commun) (f)''' : racine.<br>
'''ῥινόκερως, -έρωτος (nom commun) (m)''' : rhinocéros.<br>
'''ῥίον, -ου (nom commun) (n)''' : sommet.<br>
'''ῥιπή, -ῆς (nom commun) (f)''' : .<br>
'''ῥίπτω (verbe)''' : lancer, jeter ; projet.<br>
'''ῥίς, -νός (nom commun) (f)''' : nez.<br>
'''ῥίψασπις, -δος (nom commun) (f)''' : .<br>
'''ῥίψις, -πός (nom commun) (f)''' : entrelac.<br>
'''ῥιπίζω (verbe)''' : éventer.<br>
'''ῥιπίς, -δος (nom commun) (f)''' : éventail.<br>
'''ῥοά, -ᾶς (nom commun) (f)''' : Forme dorienne de ''ῥοή''.<br>
'''ῥοή, -ῆς (nom commun) (f)''' : écoulement.<br>
'''ῥοδόεις, -εσσα, -εν (adjectif)''' : rosé.<br>
'''ῥοδοέντως (adverbe)''' : .<br>
'''ῥοδοέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''ῥοδόεις''.<br>
'''ῥοδοέστερος, -έρα, -ερον (adjectif)''' : Comparatif de ''ῥοδόεις''.<br>
'''ῥόδον, -ου (nom commun) (n)''' : rose (fleur).<br>
'''ϝρόδον, -ου (nom commun) (n)''' : Forme éolienne de ''ῥόδον''.<br>
'''ῥοιά, -ᾶς (nom commun) (f)''' : grenade (fruit).<br>
'''ῥόκα, -ας (nom commun) (f)''' : roquette (plante).<br>
'''ῥόμος, -ου (nom commun) (m)''' : ver.<br>
'''ῥομφαία, -ας (nom commun) (f)''' : estramaçon.<br>
'''ῥόπαλον, -άλου (nom commun) (n)''' : gourdin.<br>
'''ῥοφῶ (verbe)''' : sucer.<br>
'''ῥύαξ, -κος (nom commun) (n)''' : ruisseau.<br>
'''ῥύγχος, -ους (nom commun) (n)''' : Trompe, museau. Bec d’un oiseau.<br>
'''ῥυθμός, -οῦ (nom commun) (m)''' : mouvement régulier.<br>
'''ῥύμη, -ης (nom commun) (f)''' : rue.<br>
'''ῥυπαίνω (verbe)''' : polluer.<br>
'''ῥύπανσις, -άνσεως (nom commun) (f)''' : pollution.<br>
'''ῥυπαρός, -ά, -όν (adjectif)''' : crasseux.<br>
'''ῥυπαρῶς (adverbe)''' : crasseusement.<br>
'''ῥυπαρώτατα, -, - (adverbe)''' : Superlatif de ''ῥυπαρῶς''.<br>
'''ῥυπαρώτερον, -, - (adverbe)''' : Comparatif de ''ῥυπαρῶς''.<br>
'''ῥυπαρώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ῥυπαρός''.<br>
'''ῥυπαρώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ῥυπαρός''.<br>
'''ῥύπος, -ου (nom commun) (m)''' : polluant.<br>
'''ῥυσμός, -οῦ (nom commun) (m)''' : Forme ionienne de ''ῥυθμός''.<br>
'''ῥυτίς, -δος (nom commun) (f)''' : ride.<br>
'''ῥωγμή, -ῆς (nom commun) (f)''' : fissure.<br>
'''ῥώθων, -ος (nom commun) (m)''' : narine.<br>
'''ῥωμαϊκός, -ή, -όν (adjectif)''' : romain.<br>
'''ῥωμαϊκῶς (adverbe)''' : romainement.<br>
'''ῥωμαϊκώτατα, -, - (adverbe)''' : Superlatif de ''ῥωμαϊκῶς''.<br>
'''ῥωμαϊκώτερον, -, - (adverbe)''' : Comparatif de ''ῥωμαϊκῶς''.<br>
'''ῥωμαϊκώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ῥωμαϊκός''.<br>
'''ῥωμαϊκώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ῥωμαϊκός''.<br>
'''ῥωμαλέος, -α, -ον (adjectif)''' : fort, robuste (en parlant des personnes) ; solide (en parlant des choses).<br>
'''ῥωμαλεότης, -τος (nom commun) (f)''' : force, robustesse (en parlant des personnes) ; solidité (en parlant des choses).<br>
'''ῥώμη, -ης (nom propre) (f)''' : force.<br>
'''ῥώομαι (verbe)''' : se précipiter, fondre sur.<br>
'''ῥωσικός, -ή, -όν (adjectif)''' : russe.<br>
'''ῥώξ, -γός (nom commun) (m)''' : Forme ionienne de ''ῥάξ''.<br>
'''ῥῶ (nom commun) (n)''' : rhô.<br>
'''Ῥαάβ (nom propre) (f)''' : Rahab.<br>
'''Ῥα (nom propre) (m)''' : Rê.<br>
'''Ῥᾶ (nom propre) (n)''' : Volga.<br>
'''Ῥαδάμανθυς, -άνθυος (nom propre) (m)''' : Rhadamanthe.<br>
'''Ῥαθούρης, -ου (nom propre) (m)''' : Niouserrê.<br>
'''Ῥαθωτις, -ίδος (nom propre) (m)''' : Toutânkhamon.<br>
'''Ῥαθώς, - (nom propre) (m)''' : Smenkhkarê.<br>
'''Ῥαμέσσης, -ου (nom propre) (m)''' : Ramsès.<br>
'''Ῥαμνοῦς, -ύντος (nom propre) (m)''' : Rhamnonte.<br>
'''Ῥαφαήλ (nom propre) (m)''' : Raphaël.<br>
'''Ῥεϐέκκα, -ας (nom propre) (f)''' : Rebecca.<br>
'''Ῥῆνος, -ήνου (nom propre) (m)''' : Rhin.<br>
'''Ῥίον, -ου (nom propre) (m)''' : Río (ville de Grèce).<br>
'''Ῥοδανός, -οῦ (nom propre) (m)''' : Rhône.<br>
'''Ῥόδη, -ης (nom propre) (f)''' : Rhodé.<br>
'''Ῥόδιος, - (nom propre) (m)''' : Rhodien.<br>
'''Ῥοδόπη, -ης (nom propre) (f)''' : Rhodope.<br>
'''Ῥόδος, -ου (nom propre) (f)''' : Rhodes.<br>
'''Ῥούθ (nom propre) (f)''' : Ruth.<br>
'''Ῥοῦφος, -ύφου (nom propre) (m)''' : Rufus.<br>
'''Ῥωμαῖος, -ίου (nom commun) (m)''' : Romain.<br>
'''Ῥώμη, -ης (nom propre) (f)''' : Rome.<br>
'''Ῥῶμος, -ώμου (nom propre) (m)''' : Rémus.<br>
'''Ῥώμυλος, -ύλου (nom propre) (m)''' : Romulus.<br>
'''Ῥωξάνη, -ης (nom propre) (f)''' : Roxane (Épouse d'Alexandre le Grand.)<br>
'''Ῥωσία, -ας (nom propre) (f)''' : Russie.<br>
'''Ῥωσίς, -δος (nom propre) (f)''' : Russe.<br>
'''Ῥῶς, -ώσος (nom commun) (m)''' : Russe.<br>
==Σ==
'''σάϐϐατον, -άτου (nom commun) (n)''' : shabbat.<br>
'''σαϐϐατισμός, -οῦ (nom commun) (n)''' : sabbatisme.<br>
'''σαδδουκαῖος, -ίου (nom commun) (m)''' : sadducéen.<br>
'''σάγμα, -τος (nom commun) (n)''' : bât.<br>
'''σάκκος, -ου (nom commun) (m)''' : sac.<br>
'''σάκος, -ους (nom commun) (n)''' : bouclier.<br>
'''σάκχαρις, -άρεως (nom commun) (f)''' : sucre.<br>
'''σάλπιγξ, -γoς (nom commun) (f)''' : trompette.<br>
'''σᾶμα, -άματος (nom commun) (n)''' : Forme dorienne de ''σῆμα''.<br>
'''σάμερον (adverbe)''' : Forme dorienne de ''σήμερον''.<br>
'''σάμιος, -ος, -ον (adjectif)''' : samosien.<br>
'''σαμψήρα, -ας (nom commun) (f)''' : cimeterre.<br>
'''σάνδαλον, -άλου (nom commun) (n)''' : sandale.<br>
'''σανίς, -δος (nom commun) (f)''' : planche.<br>
'''σαρδονικός, -ή, -όν (adjectif)''' : sarde.<br>
'''σαρδονικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''σαρδονικός''.<br>
'''σαρδονικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''σαρδονικός''.<br>
'''σαρδονικότατα, -, - (adverbe)''' : Superlatif de ''σαρδονικῶς''.<br>
'''σαρδονικότερον, -, - (adverbe)''' : Comparatif de ''σαρδονικῶς''.<br>
'''σαρδονικῶς (adverbe)''' : sardoniquement.<br>
'''σαρκικός, -ή, -όν (adjectif)''' : charnel.<br>
'''σαρκικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''σαρκικός''.<br>
'''σαρκικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''σαρκικός''.<br>
'''σαρκικότατα, -, - (adverbe)''' : Superlatif de ''σαρκικῶς''.<br>
'''σαρκικότερον, -, - (adverbe)''' : Comparatif de ''σαρκικῶς''.<br>
'''σαρκικῶς (adverbe)''' : charnellement.<br>
'''σάρκινος, -ίνη, -άρκινον (adjectif)''' : charnu.<br>
'''σάρξ, -κός (nom commun) (f)''' : chair.<br>
'''σατυρίασις, -άσεως (nom commun) (f)''' : satyriasis.<br>
'''σατυρικός, -ή, -όν (adjectif)''' : satyrique.<br>
'''σατύριον, -ίου (nom commun) (n)''' : satyrion.<br>
'''σατυρίσκος, -ου (nom commun) (m)''' : satyreau.<br>
'''σάτυρος, -ύρου (nom commun) (m)''' : satyre.<br>
'''σατυρώδης, -ης, -ες (adjectif)''' : satyrode.<br>
'''σαύρα, -ας (nom commun) (f)''' : lézard ; quéquette.<br>
'''σαύρη, -ης (nom commun) (f)''' : Forme ionienne de ''σαύρα''.<br>
'''σαυροματικός, -ή, -όν (adjectif)''' : sarmate.<br>
'''σαυροματικῶς (adverbe)''' : .<br>
'''σαυροματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σαυροματικός''.<br>
'''σαυροματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σαυροματικός''.<br>
'''σαῦρος, -ύρου (nom commun) (m)''' : lézard.<br>
'''σάπφειρος, -ίρου (nom commun) (f)''' : saphir.<br>
'''σαφέστερος, -άτη, -έστατον (adjectif)''' : Superlatif de ''σαφής''.<br>
'''σαφέστατος, -έρα, -έσερον (adjectif)''' : Comparatif de ''σαφής''.<br>
'''σαφής, -ής, -ές (adjectif)''' : Clair, évident ; manifeste.<br>
'''σαφῶς (adverbe)''' : Clairement, évidemment ; manifestement.<br>
'''σεϐάζω (verbe)''' : respecter.<br>
'''σεϐασμός, -οῦ (nom commun) (m)''' : respect.<br>
'''σεϐαστός, -ός, -όν (adjectif)''' : respectable ; respecté.<br>
'''σέϐομαι (verbe)''' : Craindre les dieux. Vénérer.<br>
'''σείριος, -ίου (nom commun) (m)''' : destructeur.<br>
'''σελάνα, -ας (nom commun) (f)''' : Forme dorienne de ''σελήνη''.<br>
'''σελάννα, -ας (nom commun) (f)''' : Forme éolienne de ''σελήνη''.<br>
'''σέλας, -τος (nom commun) (n)''' : Éclat, lumière ; lueur brillante.<br>
'''σελάχιον, -ίου (nom commun) (n)''' : raie (poisson).<br>
'''σέλαχος, -άχους (nom commun) (n)''' : requin.<br>
'''σελήνη, -ης (nom commun) (f)''' : lune.<br>
'''σεληνιακός, -ή, -όν (adjectif)''' : lunaire.<br>
'''σέλινον, -ίνου (nom commun) (n)''' : céleri.<br>
'''σελίς, -δος (nom commun) (f)''' : page. (Face d'une feuille de papier, de parchemin, de vélin, servant à l'écriture.)<br>
'''σεμίδαλις, -άλεως (nom commun) (f)''' : semoule.<br>
'''σεμνός, -ή, -όν (adjectif)''' : modeste.<br>
'''σεμνότατα, -, - (adverbe)''' : Superlatif de ''σεμνῶς''.<br>
'''σεμνότερον, -, - (adverbe)''' : Comparatif de ''σεμνῶς''.<br>
'''σεμνότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''σεμνός''.<br>
'''σεμνότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''σεμνός''.<br>
'''σεμνότης, -τος (nom commun) (f)''' : modestie.<br>
'''σεμνῶς (adverbe)''' : modestement.<br>
'''σῆμα, -ήματος (nom commun) (n)''' : signe.<br>
'''σημαντικός -ή -όν (adjectif)''' : significatif.<br>
'''σημαντικῶς (adverbe)''' : significativement.<br>
'''σημαντικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σημαντικός''.<br>
'''σημαντικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σημαντικός''.<br>
'''σημαντικώτατα, -, - (adverbe)''' : Superlatif de ''σημαντικῶς''.<br>
'''σημαντικώτερον, -, - (adverbe)''' : Comparatif de ''σημαντικῶς''.<br>
'''σημεῖον, -ίου (nom commun) (n)''' : signal.<br>
'''σήμερον (adverbe)''' : aujourd’hui.<br>
'''σημύδα, -ης (nom commun) (f)''' : bouleau.<br>
'''σῆραγξ, -ήραγγος (nom commun) (f)''' : tunnel.<br>
'''σήσαμον, άμου (nom commun) (n)''' : sésame.<br>
'''σής, -τός (nom commun) (m)''' : mite.<br>
'''σητόδοκις, -εως (nom commun) (f)''' : papillon.<br>
'''σῆψις, -ήψεως (nom commun) (f)''' : putréfaction.<br>
'''σιαγών, -όνος (nom commun) (f)''' : mâchoire.<br>
'''σιηγών, -όνος (nom commun) (f)''' : Forme ionienne de ''σιαγών''.<br>
'''σῖγμα (nom commun) (n)''' : sigma.<br>
'''σικάριος, -ίου (nom commun) (m)''' : assassin.<br>
'''σικελικός, -ή, -όν (adjectif)''' : sicilien.<br>
'''σικυός, -οῦ (nom commun) (m)''' : concombre.<br>
'''σικυώνιος, -ος, -ον (adjectif)''' : .<br>
'''σίλφιον, -ίου (nom commun) (n)''' : silphium.<br>
'''σιμός, -ή, -όν (adjectif)''' : Camus ; montant.<br>
'''σιμῶ (verbe)''' : lever le nez.<br>
'''σίναπι, -άπεως (nom commun) (n)''' : moutarde (plante).<br>
'''σιός, -οῦ (nom commun) (m)''' : Forme laconienne de ''θεός''.<br>
'''σίσυς, -ος (nom commun) (m)''' : fourrure.<br>
'''-σις, -εως (suffixe) (f)''' : Suffixe formant des noms d'action.<br>
'''σῖτος, -ίτου (nom commun) (m)''' : Grain, à la fois le blé et l'orge. (Par extension) Toute nourriture végétale (par opposition à la viande ou la boisson). Pension alimentaire. (Droit athénien) Distribution de blé aux indigents.<br>
'''σιωπή, -ῆς (nom commun) (f)''' : calme, silence.<br>
'''σιωπηρός, -ός, -όν (adjectif)''' : silencieux, tacite.<br>
'''σιωπηρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''σαρκικός''.<br>
'''σιωπηρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''σαρκικός''.<br>
'''σιωπηρότατα, -, - (adverbe)''' : Superlatif de ''σαρκικῶς''.<br>
'''σιωπηρότερον, -, - (adverbe)''' : Comparatif de ''σαρκικῶς''.<br>
'''σιωπηρῶς (adverbe)''' : tacitement.<br>
'''σιωπηρότης, -τος (nom commun) (f)''' : tacité.<br>
'''σιωπῶ (verbe)''' : se taire.<br>
'''σκαιός, -ά, -όν (adjectif)''' : qui est à gauche.<br>
'''σκάνδαλον, -άλου (nom commun) (n)''' : Piège placé sur le chemin.<br>
'''σκανδάληθρον, -ήθρου (nom commun) (n)''' : .<br>
'''σκανδαλίζω (verbe)''' : Placer un piège sur le chemin.<br>
'''σκαπανεύς, -έως (nom commun) (m)''' : .<br>
'''σκαπάνη, -ης (nom commun) (f)''' : .<br>
'''σκᾶπτρον, -άπτρου (nom commun) (n)''' : Forme dorienne de ''σκῆπτρον''.<br>
'''σκάπτω (verbe)''' : creuser.<br>
'''σκεδάννυμι (verbe)''' : disperser, répandre.<br>
'''σκέλος, -ους (nom commun) (n)''' : (Anatomie) Jambe de l’homme et des animaux. (Au pluriel) Les murs (entre Athènes et le Pirée, entre Mégare et Nisæa).<br>
'''σκεπτικός, -ή, -όν (adjectif)''' : observateur.<br>
'''σκεπτικῶς (adverbe)''' : sceptiquement.<br>
'''σκεπτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σκεπτικός''.<br>
'''σκεπτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σκεπτικός''.<br>
'''σκέπτομαι (verbe)''' : Considérer ; examiner avec soin.<br>
'''σκευαρίδιον, -ίου (nom commun) (n)''' : .<br>
'''σκευάριον, -ίου (nom commun) (n)''' : .<br>
'''σκευωρία, -ας (nom commun) (f)''' : machination.<br>
'''σκεῦος, -ύους (nom commun) (n)''' : ustensile.<br>
'''σκέψις, -εως (nom commun) (f)''' : pensée. (opération de l’intelligence.)<br>
'''σκηπτός, -οῦ (nom commun) (m)''' : ouragan.<br>
'''σκῆπτρον, -ήπτρου (nom commun) (n)''' : Étai, sceptre.<br>
'''σκήπτω (verbe)''' : Étayer, soutenir. Brandir.<br>
'''σκίουρος, -ύρου (nom commun) (m)''' : écureuil.<br>
'''σκολιός, -ά, -όν (adjectif)''' : tordu.<br>
'''σκολόπαξ, -άκος (nom commun) (m)''' : bécasse.<br>
'''σκολόπενδρα, -ένδρας (nom commun) (f)''' : scolopendre.<br>
'''σκολοπένδριον, -ίου (nom commun) (n)''' : Diminutif de ''σκολόπενδρα''.<br>
'''σκόλοψ, -πος (nom commun) (m)''' : écharde.<br>
'''σκόπελος, -έλου (nom commun) (m)''' : écueil.<br>
'''σκοπῶ (verbe)''' : observer.<br>
'''σκόροδον, -όδου (nom commun) (n)''' : ail.<br>
'''σκορπιός, -οῦ (nom commun) (m)''' : scorpion.<br>
'''σκοτεινός, -ή, -όν (adjectif)''' : obscur.<br>
'''σκοτεινῶς (adverbe)''' : obscurément.<br>
'''σκοτεινώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σκοτεινός''.<br>
'''σκοτεινώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σκοτεινός''.<br>
'''σκότιος, -ία, -ότιον (adjectif)''' : bâtard.<br>
'''σκότος, -ου (nom commun) (m)''' : obscurité.<br>
'''σκυθικός, -ή, -όν (adjectif)''' : scythe.<br>
'''σκυθικῶς (adverbe)''' : .<br>
'''σκυθικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σκυθικός''.<br>
'''σκυθικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σκυθικός''.<br>
'''σκύλαξ, -κος (nom commun) (m)''' : chiot.<br>
'''σκυλεύω (verbe)''' : dépouiller.<br>
'''σκύλλω (verbe)''' : Déchirer, troubler.<br>
'''σκῦλον, -ύλου (nom commun) (n)''' : butin.<br>
'''σκῦτος, -ύτου (nom commun) (m)''' : .<br>
'''σκώληξ, -κος (nom commun) (m)''' : ver.<br>
'''σκῶμμα, -ώμματος (nom commun) (n)''' : .<br>
'''σκωραμίς, -δος (nom commun) (f)''' : pot de chambre.<br>
'''σκωρία, -ας (nom commun) (f)''' : scorie.<br>
'''σκῶρ, -ατός (nom commun) (n)''' : excrément.<br>
'''σκώψ, -πός (nom commun) (m)''' : hibou.<br>
'''σμάραγδος, -άγδου (nom commun) (f)''' : émeraude.<br>
'''σμάω (verbe)''' : frotter, nettoyer.<br>
'''σμῆγμα, -ήγματος (nom commun) (n)''' : savon, détergent ; onguent.<br>
'''σμήχω (verbe)''' : essuyer.<br>
'''σμῖλαξ, -ίλακος (nom commun) (f)''' : if.<br>
'''σμίλη, -ης (nom commun) (f)''' : Ciseau. Bistouri, lancette.<br>
'''σμύραινα, -ίνης (nom commun) (f)''' : murène.<br>
'''σμῶδιξ, -ώδιγγος (nom commun) (f)''' : contusion.<br>
'''σμώχω (verbe)''' : frotter.<br>
'''σοϐαρός, -ή, -όν (adjectif)''' : Effrayant. Fuyant, rapide. Hautain, dédaigneux ; pompeux.<br>
'''σοϐαρῶς (adverbe)''' : Effrayamment, rapidement. Hautainement, dédaigneusement ; pompeusement.<br>
'''σοϐαρώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σοϐαρός''.<br>
'''σοϐαρώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σοϐαρός''.<br>
'''σοϐῶ (verbe)''' : Chasser, effrayer les oiseaux. Bouger rapidement.<br>
'''σαπρός, -ή, -όν (adjectif)''' : Pourri, putride<br>
'''σαπρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''σαπρός''.<br>
'''σαπρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''σαπρός''.<br>
'''σαπρῶς (adverbe)''' : putridement.<br>
'''σός, -ή, -όν (adjectif possessif)''' : ton.<br>
'''σοφία, -ας (nom commun) (f)''' : sagesse.<br>
'''σοφίη, -ης (nom commun) (f)''' : Forme ionienne de ''σοφία''.<br>
'''σοφός, -ή, -όν (adjectif)''' : Habile. (En parlant de l’intelligence ou du caractère) Prudent, sage. (En particulier) Initié à la sagesse. Ingénieux, fin, rusé.<br>
'''σοφῶς (adverbe)''' : Habilement, sagement. Ingénieusement, finement.<br>
'''σοφώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σοφός''.<br>
'''σοφώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σοφός''.<br>
'''σπαδωνισμός, -οῦ (nom commun) (m)''' : castration.<br>
'''σπάδων, -ος (nom commun) (m)''' : castrat ; eunuque.<br>
'''σπάθη, -ης (nom commun) (f)''' : épée.<br>
'''σπαθίον, -ου (nom commun) (m)''' : Diminutif de ''σπάθη''.<br>
'''σπανακόν, -οῦ (nom commun) (n)''' : épinard.<br>
'''σπάναξ, -κος (nom commun) (m)''' : épinard.<br>
'''σπάνιος, -ία, -ιον (adjectif)''' : rare.<br>
'''σπανίως (adverbe)''' : rarement.<br>
'''σπανιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σπάνιος''.<br>
'''σπανιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σπάνιος''.<br>
'''σπαργάνιον, -ίου (nom commun) (n)''' : rubanier.<br>
'''σπάργανον, -άνου (nom commun) (n)''' : lange.<br>
'''σπάργω (verbe)''' : langer.<br>
'''σπαρτιατικός, -ή, -όν (adjectif)''' : spartiate.<br>
'''σπασμός, -οῦ (nom commun) (m)''' : spasme.<br>
'''σπαστικός, -ή, -όν (adjectif)''' : .<br>
'''σπάω (verbe)''' : briser.<br>
'''σπεῖρα, -ίρας (nom commun) (f)''' : spire.<br>
'''σπεῖρον, -ίρου (nom commun) (n)''' : .<br>
'''σπέος, -ους (nom commun) (n)''' : grotte.<br>
'''σπεύδω (verbe)''' : se hâter.<br>
'''σπήλαιον, -ίου (nom commun) (n)''' : caverne, grotte ; cavité.<br>
'''σπῆλυγξ, -ήλυγγος (nom commun) (m)''' : caverne, antre ; grotte.<br>
'''σπογγιά, -ᾶς (nom commun) (f)''' : éponge.<br>
'''σπόγγος, -ου (nom commun) (m)''' : éponge ; (anatomie) amygdale.<br>
'''σποδός, -οῦ (nom commun) (m)''' : cendre.<br>
'''σπολεύς, -έως (nom commun) (m)''' : sorte de pain.<br>
'''σπονδεῖος, -ίου (nom commun) (m)''' : spondée.<br>
'''σπόνδυλος, -ύλου (nom commun) (m)''' : Forme attique de ''σφόνδυλος''.<br>
'''σπουδαῖος, -ία, -ῖον (adjectif)''' : important.<br>
'''σπουδή, -ῆς (nom commun) (f)''' : hâte.<br>
'''στάδιον, -ου (nom commun) (n)''' : stade. (env. 180 m)<br>
'''σταγών, -όνος (nom commun) (f)''' : goutte.<br>
'''στάζω (verbe)''' : couler goutte à goutte.<br>
'''σταθερός, -ή, -όν (adjectif)''' : fixe, constant, ferme.<br>
'''στακτός, -οῦ (nom commun) (m)''' : cendre.<br>
'''στάλα, -ας (nom commun) (f)''' : Forme dorienne de ''στήλη''.<br>
'''στάλαγμα, -άγματος (nom commun) (n)''' : goutte.<br>
'''σταλαγμός, -οῦ (nom commun) (m)''' : écoulement, égouttage.<br>
'''σταλάσσω (verbe)''' : tomber, couler.<br>
'''στάλλα, -ας (nom commun) (f)''' : Forme éolienne de ''στήλη''.<br>
'''στάσις, -εως (nom commun) (f)''' : .<br>
'''σταυρός, -οῦ (nom commun) (m)''' : croix.<br>
'''σταυρόω (verbe)''' : crucifier.<br>
'''σταύρωσις, -ώσεως (nom commun) (f)''' : crucifixion.<br>
'''σταφυλή, -ῆς (nom commun) (f)''' : grappe de raisin mûr.<br>
'''σταφυλίς, -δος (nom commun) (f)''' : luette.<br>
'''στέαρ, -ατος (nom commun) (n)''' : Graisse compacte ; lard ; suif. Graisse.<br>
'''στεατοπυγός, -ός, -όν (adjectif)''' : Qui a de grosses fesses.<br>
'''στέγασις, -άσεως (nom commun) (f)''' : accommodation.<br>
'''στεγανός, -ή, -όν (adjectif)''' : étanche, imperméable.<br>
'''στεγανῶς (adverbe)''' : .<br>
'''στεγανώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στεγανός''.<br>
'''στεγανώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''στεγανός''.<br>
'''στέγος, -ους (nom commun) (n)''' : Abri. Toit. Maison. Tombeau. Urne funéraire.<br>
'''στέγω (verbe)''' : Couvrir. Supporter, résister.<br>
'''στεῖρος, -ίρα, -ῖρον (adjectif)''' : stérile.<br>
'''στειρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''στεῖρος''.<br>
'''στειρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''στεῖρος''.<br>
'''στείρως (adverbe)''' : stérilement.<br>
'''στελεά, -ᾶς (nom commun) (f)''' : axe, pôle.<br>
'''στελεόν, -οῦ (nom commun) (n)''' : manche.<br>
'''στέλεχος, -έχους (nom commun) (n)''' : .<br>
'''στέλλω (verbe)''' : .<br>
'''στέμμα, -τος (nom commun) (n)''' : guirlande.<br>
'''στεμματηφορῶ (verbe)''' : .<br>
'''στεμματιαῖον, -ίου (nom commun) (n)''' : .<br>
'''στεμματία, -ας (nom commun) (f)''' : .<br>
'''στεμματοφορία, -ας (nom commun) (n)''' : .<br>
'''στεμματοφόρος, -ό, -όν (adjectif)''' : étroit, resserré.<br>
'''στεμματῶ (verbe)''' : .<br>
'''στενός, -ή, -όν (adjectif)''' : étroit, resserré.<br>
'''στενῶς (adverbe)''' : étroitement.<br>
'''στενώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στενός''.<br>
'''στενώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''στενός''.<br>
'''στερεός, -ά, -όν (adjectif)''' : ferme, dur.<br>
'''στερέωμα, -ώματος (nom commun) (n)''' : firmament.<br>
'''στερεῶς (adverbe)''' : fermement, durement.<br>
'''στερεώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στερεός''.<br>
'''στερεώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''στερεός''.<br>
'''στερεῶ (verbe)''' : .<br>
'''στέφανος, -άνου (nom commun) (m)''' : cercle d'une armée sur un champ de bataille. Couronne.<br>
'''στέφω (verbe)''' : couronner.<br>
'''στηθόδεσμος -ου (nom commun) (m)''' : .<br>
'''στῆθος, -ήθους (nom commun)''' : poitrine.<br>
'''στήλη, -ης (nom commun) (f)''' : stèle.<br>
'''στήνιον, -ίου (nom commun) (n)''' : sein.<br>
'''στιϐάς, -δος (nom commun) (f)''' : .<br>
'''στίϐι, -τος (nom commun) (n)''' : antimoine.<br>
'''στιγμή, -ῆς (nom commun) (f)''' : moment.<br>
'''στοργή, -ῆς (nom commun) (f)''' : amour familial.<br>
'''στοιχεῖον, -ίου (nom commun) (n)''' : élément.<br>
'''στοιχειωδέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''στοιχειώδης''.<br>
'''στοιχειωδέστερος, -έρη, -έστερον (adjectif)''' : Comparatif de ''στοιχειώδης''.<br>
'''στοιχειώδης, -ης, -ῶδες (m)''' : élémentaire.<br>
'''στοιχειωδῶς (adverbe)''' : élémentairement.<br>
'''στολίζω (verbe)''' : .<br>
'''στόμα, -τος (nom commun) (n)''' : bouche.<br>
'''στομάχιον, -ίου (nom commun) (n)''' : estomac.<br>
'''στώμυλμα, -τος (nom commun) (n)''' : bavard.<br>
'''στοχάζομαι (verbe)''' : Conjecturer. Viser.<br>
'''στοχάς, -δος (nom commun) (f)''' : cible.<br>
'''στοχαστής, -οῦ (nom commun) (m)''' : Conjectureur, penseur.<br>
'''στοχαστικός, -ή, -όν (adjectif)''' : Qui vise bien, qui tend directement vers. Habile à conjecturer, conjectural.<br>
'''στοχαστικῶς (adverbe)''' : conjecturalement.<br>
'''στοχαστικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στοχαστικός''.<br>
'''στοχαστικώτερος, -έρη, -ώτερον (adjectif)''' : Comparatif de ''στοχαστικός''.<br>
'''στόχος, -ου (nom commun) (m)''' : Cible, but, point visé. Conjecture.<br>
'''στορέννυμι (verbe)''' : étendre, recouvrir.<br>
'''στραγγαλίζω (verbe)''' : étrangler.<br>
'''στραγγαλίς, -δος (nom commun) (f)''' : nœud.<br>
'''στραγγαλοῦμαι (verbe)''' : tordre.<br>
'''στραγγίζω (verbe)''' : essorer.<br>
'''στραγγουρία, -ας (nom commun) (f)''' : strangurie.<br>
'''στράγξ, -γός (nom commun) (f)''' : goutte.<br>
'''στρατός, -οῦ (nom commun) (m)''' : armée.<br>
'''στρατηγός, -οῦ (nom commun) (m)''' : général.<br>
'''στραταγός, -οῦ (nom commun) (m)''' : Forme arcadienne et dorienne de ''στρατηγός''.<br>
'''στρατήγημα, -ήματος (nom commun) (n)''' : stratagème.<br>
'''στρατηγία, -ας (nom commun) (f)''' : stratégie.<br>
'''στρέμμα, -τος (nom commun) (n)''' : Tournure ; rouleau. Conspiration.<br>
'''στρεπτικός, -ή, -όν (adjectif)''' : .<br>
'''στρεπτικῶς (adverbe)''' : .<br>
'''στρεπτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στρεπτικός''.<br>
'''στρεπτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''στρεπτικός''.<br>
'''στρεπτικώτατα, -, - (adverbe)''' : Superlatif de ''στρεπτικῶς''.<br>
'''στρεπτικώτερον, -, - (adverbe)''' : Comparatif de ''στρεπτικῶς''.<br>
'''στρεπτός, -ή, -όν (adjectif)''' : Tourné ; docile.<br>
'''στρεπτῶς (adverbe)''' : docilement.<br>
'''στρεπτώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στρεπτός''.<br>
'''στρεπτώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''στρεπτός''.<br>
'''στρέφω (verbe)''' : Tourner, retourner.<br>
'''στρέψις, -εως (nom commun) (f)''' : .<br>
'''στρίζω (verbe)''' : strider.<br>
'''στρίξ, -γός (nom commun) (f)''' : chouette.<br>
'''στρόταγος, -άγου (nom commun) (m)''' : Forme éolienne de ''στρατηγός''.<br>
'''στρατιώτης, -ου (nom commun) (m)''' : soldat.<br>
'''στρόϐιλος, -ίλου (nom commun) (m)''' : Ce qui tourne ou tournoie. Toupie. Tourbillon, ouragan. Objet divers en spirale ou de forme conique. Pomme de pin ou fruit des arbres résineux. Coquillage en spirale. Enroulement du hérisson sur lui-même. Qui tournoie en spirale.<br>
'''στροϐιλόω (verbe)''' : tourner.<br>
'''στραγγός, -ή, -όν (adjectif)''' tordu.<br>
'''στρογγύλλω (verbe)''' : arrondir.<br>
'''στρογγύλος, -η, -ον (adjectif)''' : arrondi.<br>
'''στρογγυλότης, -τος (nom commun) (f)''' : rondeur.<br>
'''στρουθιοκάμηλος, -ήλου (nom commun) (m)''' : autruche.<br>
'''στρουθίον, -ου (nom commun) (n)''' : moineau.<br>
'''στροφή, -ῆς (nom commun) (f)''' : tour.<br>
'''στρόφιγξ, -γος (nom commun) (m)''' : charnière.<br>
'''στρόφιον, -ίου (nom commun) (n)''' : strophium.<br>
'''στρόφος, -ου (nom commun) (m)''' : corde.<br>
'''στύφω (verbe)''' : contracter.<br>
'''στῦψις, -ύψεως (nom commun) (f)''' : contraction.<br>
'''στρῶμα, -ώματος (nom commun) (n)''' : matelas.<br>
'''στρωμνή, -ῆς (nom commun) (f)''' : matelas.<br>
'''στρώννυμι (verbe)''' : .<br>
'''στυγερός, -ά, -όν (adjectif)''' : horrible.<br>
'''στυγερώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''στυγερός''.<br>
'''στυγερώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''στυγερός''.<br>
'''στυγερῶς (adverbe)''' : horriblement.<br>
'''στῦλος, -ύλου (nom commun) (m)''' : Pilier, colonne.<br>
'''στῦσις, -ύσεως (nom commun) (f)''' : érection (action physiologique).<br>
'''στύω (verbe)''' : avoir une érection.<br>
'''συγάτηρ, -τρός (nom commun) (f)''' : Forme dorienne de ''θυγάτηρ''.<br>
'''συγγνώμη, -ης (nom commun) (f)''' : .<br>
'''συγγιγνώσκω (verbe)''' : .<br>
'''συγκέντρωσις, -ώσεως (nom commun) (f)''' : concentration.<br>
'''συγκεντρῶ (verbe)''' : concentrer.<br>
'''συγκεράννυμι (verbe)''' : .<br>
'''συγκρητισμός, -οῦ (nom commun) (m)''' : .<br>
'''συγκινῶ (verbe)''' : émouvoir.<br>
'''συγκίνησις, -ήσεως (nom commun) (f)''' : émotion.<br>
'''συγκινητικός, -ή, -όν (adjectif)''' : émouvant.<br>
'''συγκρίνω (verbe)''' : comparer.<br>
'''σύγκρισις, -ίσεως (nom commun) (f)''' : comparaison.<br>
'''συγκριτικός, -ή, -όν (adjectif)''' : comparatif.<br>
'''σύγκρουσις, -ύσεως (nom commun) (f)''' : collision.<br>
'''συγχώρησις, -εως (nom commun) (f)''' : .<br>
'''συγχωρητέος, -α, -ον (adjectif)''' : .<br>
'''συζήτησις, -ήσεως (nom commun) (f)''' : discussion, conversation ; débat.<br>
'''συζητῶ (verbe)''' : discuter, converser ; débattre.<br>
'''σῦκον, -ύκου (nom commun) (n)''' : Figue ; vulve.<br>
'''συκοφάντης, -ου (nom commun) (m)''' : Délateur, calomniateur. Chicaneur de mauvaise foi.<br>
'''συκοφαντικός, -ή, -όν (adjectif)''' : flagorneur.<br>
'''συκοφαντικῶς (adverbe)''' : flagorneusement.<br>
'''συκοφαντικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''συκοφαντικός''.<br>
'''συκοφαντικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''συκοφαντικός''.<br>
'''συκοφαντικώτατα, -, - (adverbe)''' : Superlatif de ''συκοφαντικῶς''.<br>
'''συκοφαντικώτερον, -, - (adverbe)''' : Comparatif de ''συκοφαντικῶς''.<br>
'''σύκχος, -ους (nom commun) (n)''' : pantoufle.<br>
'''συλάω (verbe)''' : Saisir, prendre ; emporter. Dépouiller, prendre les armes de son ennemi mort. Piller.<br>
'''σύλη, -ης (nom commun) (f)''' : Prise ; saisie.<br>
'''σύλησις, -ήσεως (nom commun) (f)''' : profanation.<br>
'''σύλον, -ου (nom commun) (n)''' : Forme attique de ''ξύλον''.<br>
'''συλλαμϐάνω (verbe)''' : .<br>
'''σύλληψις, -ήψεως (nom commun) (f)''' : Action de prendre ensemble. Compréhension. Réunion par prononciation de deux voyelles. Action de s’emparer, de saisir. Conception dans le sein de la mère. Assistance, secours.<br>
'''συμϐάλλω (verbe)''' : .<br>
'''σύμϐασις, -άσεως (nom commun) (f)''' : convention.<br>
'''συμϐίωσις, -ώσεως (nom commun) (f)''' : vie commune.<br>
'''συμϐιῶ (verbe)''' : vivre ensemble.<br>
'''συμϐόλαιον, -ίου (nom commun) (n)''' : contrat.<br>
'''συμϐολικός, -ή, -όν (adjectif)''' : relatif aux signes de reconnaissance.<br>
'''σύμϐολον, -όλου (nom commun) (n)''' : signe de reconnaissance.<br>
'''συμμετρία, -ας (nom commun) (f)''' : bonne proportion.<br>
'''συμπάθεια, -ίας (nom commun) (f)''' : Communauté de sentiments ou d’impressions. (Philosophie), (Terme stoïcien) Rapport de certaines choses entre elles.<br>
'''συμπεραίνω (verbe)''' : accomplir, finir, trancher, décider.<br>
'''συμπέρασμα, -άσματος (nom commun) (n)''' : conclusion.<br>
'''συμπερασματικός, -ή, -όν (adjectif)''' : conclusif.<br>
'''συμπερασματικῶς (adverbe)''' : conclusivement.<br>
'''συμπεριφέρομαι (verbe)''' : se comporter.<br>
'''συμπεριφορά, -ᾶς (nom commun) (f)''' : comportement.<br>
'''συμπεριφορικός, -ή, -όν (adjectif)''' : comportemental.<br>
'''συμπεριφορισμός, -οῦ (nom commun) (m)''' : comportementalisme.<br>
'''συμπεριφοριστής, -οῦ (nom commun) (m)''' : comportementaliste.<br>
'''συμπίνω (verbe)''' : festoyer.<br>
'''συμπονῶ (verbe)''' : compatir.<br>
'''συμπόσιον, -ίου (nom commun) (n)''' : Banquet, festin. (Collectif) Les convives. Salle de festin.<br>
'''σύμπτωμα, -ώματος (nom commun) (n)''' : Accident, malchance ; symptôme.<br>
'''σύμπτωσις, -ώσεως (nom commun) (f)''' : coïncidence.<br>
'''συμπίπτω (verbe)''' : coïncider.<br>
'''συμφορά, -ᾶς (nom commun) (f)''' : désastre.<br>
'''συμφοράζω (verbe)''' : .<br>
'''συμφοραίνω (verbe)''' : .<br>
'''συμφορηδόν, -οῦ (nom commun) (n)''' : .<br>
'''συμφόρημα, -ήματος (nom commun) (n)''' : .<br>
'''συμφόρησις, -ήσεως (nom commun) (f)''' : congestion.<br>
'''συμφορή, -ῆς (nom commun) (f)''' : Forme ionienne de ''συμφορά''.<br>
'''συμφέρω (verbe)''' : accumuler.<br>
'''συμφύρομαι (verbe)''' : .<br>
'''σύμφυσις, -εως (nom commun) (f)''' : symphyse.<br>
'''σύμφωνον, -ώνου (nom commun) (n)''' : consonne.<br>
'''συναγωγή, -ῆς (nom commun) (f)''' : Rassemblement, assemblée de gens. Rassemblement, regroupement, mis en tas, etc., de choses.<br>
'''συνάγω (verbe)''' : rassembler.<br>
'''συναίσθημα, -ήματος (nom commun) (n)''' : émotion.<br>
'''συναισθάνομαι (verbe)''' : .<br>
'''σύν (préposition)''' : Avec, à côté de.<br>
'''σύν- (préfixe)''' (Devient ''σύγ-'' devant ''γ'', ''κ'', ''ξ'', ''χ'' ; ''σύλ-'' devant ''λ'' ; ''σύμ-'' devant ''β'', ''π'', ''φ'', ''μ'', ''ψ''.) : syn-.<br>
'''σύναψις, -άψεως (nom commun) (f)''' : connexion.<br>
'''συνάπτω (verbe)''' : connecter.<br>
'''συνέδριον, -ίου (nom commun) (n)''' : conseil, congrégation.<br>
'''σύνεδρος, -έδρου (nom commun) (n)''' : conseiller, congrégationniste.<br>
'''συνείδησις, -ήσεως (nom commun) (f)''' : conscience.<br>
'''συνεργάτης, -ου (nom commun) (m)''' : collaborateur.<br>
'''σύνεσις, -έσεως (nom commun) (f)''' : connexion.<br>
'''συνέχεια, -ίας (nom commun) (f)''' : continuité.<br>
'''συνεχής, -ής, -ές (adjectif)''' : continu.<br>
'''συνεχέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''συνεχής''.<br>
'''συνεχέστερος, -έρα, -έστερον (adjectif)''' : Comparatif de ''συνεχής''.<br>
'''συνεχότατα, -, - (adverbe)''' : Superlatif de ''συνεχῶς''.<br>
'''συνεχότερον, -, - (adverbe)''' : Comparatif de ''συνεχῶς''.<br>
'''συνεχῶς (adverbe)''' : continuellement.<br>
'''συνίημι (verbe)''' : rejoindre.<br>
'''συνίστημι (verbe)''' : .<br>
'''συνουσία, -ας (nom commun) (f)''' : copulation.<br>
'''συνουσιάζω (verbe)''' : copuler.<br>
'''σύνοψις, -όψεως (nom commun) (f)''' : Vue d'ensemble. Coup d'œil général. Table des matières. (Figuré) Examen.<br>
'''σύνταγμα, -άγματος (nom commun) (n)''' : constitution.<br>
'''συνταγματικός, -ή, -όν (adjectif)''' : constitutionnel.<br>
'''συνταγματικότατα, -, - (adverbe)''' : Superlatif de ''συνταγματικῶς''.<br>
'''συνταγματικότερον, -, - (adverbe)''' : Comparatif de ''συνταγματικῶς''.<br>
'''συνταγματικότης, -τος (nom commun) (f)''' : constitutionnalité.<br>
'''συνταγματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''συνταγματικός''.<br>
'''συνταγματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''συνταγματικός''.<br>
'''συνταγματικῶς (adverbe)''' : constitutionnellement.<br>
'''σύνταξις, -άξεως (nom commun) (f)''' : mise en ordre.<br>
'''συντάσσω (verbe)''' : mettre en ordre.<br>
'''συνύπαρξις, -άρξεως (nom commun) (f)''' : coexistence.<br>
'''συνυπάρχω (verbe)''' : coexister.<br>
'''συνωμοσία, -ας (nom commun) (f)''' : conspiration.<br>
'''συνωμότης, -ου (nom commun) (m)''' : conspirateur.<br>
'''συνωμότρια, -ας (nom commun) (f)''' : conspiratrice.<br>
'''συνωμοτῶ (verbe)''' : conspirer.<br>
'''σῦριγξ, -ύριγγος (nom commun) (f)''' : Roseau. (Musique) Flûte de Pan.<br>
'''συριστί (adverbe)''' : .<br>
'''σύρξ, -κός (nom commun) (f)''' : Forme éolienne de ''σάρξ''.<br>
'''σύρραξις, -άξεως (nom commun) (f)''' : .<br>
'''συρτός, -οῦ (nom commun) (m)''' : tiroir.<br>
'''συσκευή, -ῆς (nom commun) (f)''' : appareil.<br>
'''συσσώρευσις, -ύσεως (nom commun) (f)''' : accumulation.<br>
'''σύσταμα, -άματος (nom commun) (n)''' : Forme dorienne de ''σύστημα''.<br>
'''συστέλλω (verbe)''' : Resserrer, contracter. Réprimer.<br>
'''σύστημα, -ήματος (nom commun) (n)''' : Réunion en un unique corps.<br>
'''συστολή, -ῆς (nom commun) (f)''' : Resserrement, contraction. Répression.<br>
'''σύ (pronom personnel)''' : tu.<br>
'''σφαγεύς, -έως (nom commun) (m)''' : tueur.<br>
'''σφαγή, -ῆς (nom commun) (f)''' : abattage.<br>
'''σφάζω (verbe)''' : tuer, sacrifier.<br>
'''σφαῖρα, -ίρας (nom commun) (f)''' : Balle, ballon ; globe.<br>
'''σφάκελος, -έλου (nom commun) (m)''' : nécrose.<br>
'''σφάλλω (verbe)''' : Faire tomber. faire chuter ; renverser. Défaire, avoir le dessus. Avoir lieu (bien ou mal tomber). Tromper, abuser. (Au passif) Se tromper, fauter. Emballer, rouler.<br>
'''σφάλμα, -τος (nom commun) (n)''' : Chute, faux pas. Erreur. Perte.<br>
'''σφεδανός, -ή, -όν (adjectif)''' : .<br>
'''σφένδαμνος, -άμνου (nom commun) (f)''' : érable.<br>
'''σφενδόνη, -ης (nom commun) (f)''' : fronde.<br>
'''σφενδονήτης, -ου (nom commun) (m)''' : frondeur.<br>
'''σφενδονητικός, -ή, -όν (adjectif)''' : frondeur.<br>
'''σφενδονητικῶς (adverbe)''' : -ment.<br>
'''σφενδονητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σφενδονητικός''.<br>
'''σφενδονητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σφενδονητικός''.<br>
'''σφενδονητικώτατα, -, - (adverbe)''' : Superlatif de ''σφενδονητικῶς''.<br>
'''σφενδονητικώτερον, -, - (adverbe)''' : Comparatif de ''σφενδονητικῶς''.<br>
'''σφήν, -ός (nom commun) (m)''' : coin (instrument).<br>
'''σφηνάριον, -ίου (nom commun) (n)''' : .<br>
'''σφηνεύς, -έως (nom commun) (m)''' : coin.<br>
'''σφηνίσκος, -ου (nom commun) (m)''' : .<br>
'''σφηνοειδής, -ής, -ές (adjectif)''' : cunéiforme.<br>
'''σφηνοκέφαλος, -ος, -ον (adjectif)''' : .<br>
'''σφηνόπους, -δός (nom commun) (m)''' : .<br>
'''σφηνοπώγων, -ονος (nom commun) (m)''' : coin.<br>
'''σφηνῶ (verbe)''' : .<br>
'''σφήνωσις, -ώσεως (nom commun) (f)''' : .<br>
'''σφήξ, -ῆκος (nom commun) (f)''' : guêpe.<br>
'''σφίγγω (verbe)''' : attacher fortement.<br>
'''σφιγκτήρ, -ῆρος (nom commun) (m)''' : sphincter.<br>
'''σφιγμός, -οῦ (nom commun) (m)''' : .<br>
'''σφόγγος, -ου (nom commun) (m)''' : Forme attique de ''σπόγγος''.<br>
'''σφοδρός, -ά, -όν (adjectif)''' : véhément.<br>
'''σφοδρότατα, -, - (adverbe)''' : Superlatif de ''σφοδρῶς''.<br>
'''σφοδρότερον, -, - (adverbe)''' : Comparatif de ''σφοδρῶς''.<br>
'''σφοδρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''σφοδρός''.<br>
'''σφοδρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''σφοδρός''.<br>
'''σφοδρότης, -τος (nom commun) (f)''' : véhémence.<br>
'''σφοδρῶς (adverbe)''' : véhémentement.<br>
'''σφόνδυλος, -ύλου (nom commun) (m)''' : vertèbre.<br>
'''σφραγίς, -δος (nom commun) (f)''' : sceau.<br>
'''σφράγισμα, -ατος (nom commun) (n)''' : .<br>
'''σφραγισμός, -οῦ (nom commun) (m)''' : .<br>
'''σφραγιστήρ, -ῆρος (nom commun) (m)''' : .<br>
'''σφραγιστήριον, -ίου (nom commun) (n)''' : .<br>
'''σφραγιστής, -οῦ (nom commun) (m)''' : .<br>
'''σφραγιστός, -ή, -όν (adjectif)''' : .<br>
'''σφυγμός, -οῦ (nom commun) (m)''' : pouls.<br>
'''σφύζω (verbe)''' : .<br>
'''σφύξις, -εως (nom commun) (f)''' : palpitation.<br>
'''σφῦρα, -ύρας (nom commun) (f)''' : marteau.<br>
'''σφώ (pronom personnel)''' : vous (vous deux).<br>
'''σχεδίασμα, -τος (nom commun) (n)''' : caprice.<br>
'''σχῆμα, -ήματος (nom commun) (n)''' : Manière d'être. Forme, figure, extérieur. Apparence, faux-semblant.<br>
'''σχίζω (verbe)''' : Fendre, séparer en fendant. Séparer en douze parts, avec l’idée de violence. Déchirer la peau avec ses griffes. Fendre, séparer, partager en deux.<br>
'''σχίσμα, -ατος (nom commun) (n)''' : division.<br>
'''σχοῖνος, -ίνου (nom commun) (m)''' : corde.<br>
'''σχολαστικός, -ή, -όν (adjectif)''' : Désœuvré. Inoccupé ; studieux.<br>
'''σχολαστικός, -οῦ (nom commun) (m)''' : Homme d'étude. (Péjoratif) Homme d'étude détaché des réalités de la vie ; pédant, nigaud, etc.<br>
'''σχολεῖον, -ίου (nom commun) (n)''' : école.<br>
'''σχολή, -ῆς (nom commun) (f)''' : Repos. Temps libre. Philosophie, méditation. École.<br>
'''σχολιάζω (verbe)''' : commenter.<br>
'''σχολιαστής, -οῦ (nom commun) (m)''' : commentateur.<br>
'''σχολιάστρια, -ας (nom commun) (f)''' : commentatrice.<br>
'''σχολιογράφος, -ου (nom commun) (m/f)''' : chroniqueur.<br>
'''σχόλιον, -ίου (nom commun) (n)''' : commentaire.<br>
'''σῴζω (verbe)''' : sauver.<br>
'''σωλήν, -ῆνος (nom commun) (m)''' : Tube ; tuyau.<br>
'''σωληνοειδής -ής -ές (adjectif)''' : .<br>
'''σῶμα, -ώματος (nom commun) (n)''' : corps.<br>
'''σωματικός, -ή, -όν (adjectif)''' : corporel.<br>
'''σωματικῶς (adverbe)''' : corporellement.<br>
'''σωματικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''σωματικός''.<br>
'''σωματικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''σωματικός''.<br>
'''σωματικώτατα, -, - (adverbe)''' : Superlatif de ''σωματικῶς''.<br>
'''σωματικώτερον, -, - (adverbe)''' : Comparatif de ''σωματικῶς''.<br>
'''σωμάτιον, -ίου (nom commun) (n)''' : codex.<br>
'''σώος, -α, -ον (adjectif)''' : sain.<br>
'''σωτήρ, -ῆρος (nom commun) (m)''' : Sauveur ; libérateur.<br>
'''σωφρονιστήριον, -ίου (nom commun) (n)''' : .<br>
'''σωφρονιστήρ, -ῆρος (nom commun) (m)''' : .<br>
'''σωφρονίζω (verbe)''' : modérer, tempérer.<br>
'''σωφροσύνη, -ης (nom commun) (f)''' : modération, tempérence.<br>
'''σώφρων, -ων, -ῶφρον (adjectif)''' : prudent.<br>
'''σϝάδυς, -εια, -υ (adjectif)''' : Forme ancienne de ''ἡδύς''.<br>
'''Σάϊς, -εως (nom commun) (f)''' : Saïs.<br>
'''Σαλμακίς, - (nom commun) (f)''' : Salmacis.<br>
'''Σαλομών, -ος (nom propre) (m)''' : Salomon.<br>
'''Σαλμωνεύς, -έως (nom propre) (m)''' : Salmonée.<br>
'''Σαμάρεια, -ίας (nom propre) (f)''' : Samarie.<br>
'''Σαμοθρᾴκη, -ης (nom propre) (f)''' : Samothrace.<br>
'''Σαμόθρᾳξ, -κος (nom commun) (m)''' : Samothracien.<br>
'''Σάμος, -ου (nom propre) (f)''' : Samos.<br>
'''Σανδρόκυπτος, -ύπτου (nom propre) (m)''' : Chandragupta.<br>
'''Σαγχουνιάθων, - (nom propre) (m)''' : Sanchoniathon.<br>
'''Σαούλ (nom propre) (m)''' : Saül.<br>
'''Σαπφώ, -οῦς (nom propre) (f)''' : Sapphô.<br>
'''Σαπώρης, -ου (nom commun) (m)''' : Shapur.<br>
'''Σάρα, -ας (prénom) (f)''' : Sarah.<br>
'''Σαρδόνιος, -ίου (nom commun) (m)''' : Sarde.<br>
'''Σαρδώ, -οῦς (nom propre) (f)''' : Sardaigne.<br>
'''Σαυρομάτης, -ου (nom commun) (m)''' : Sarmate.<br>
'''Σαυροματία, -ας (nom propre) (f)''' : Sarmatie.<br>
'''Σαυρομάτις, -δος (nom commun) (f)''' : Sarmate.<br>
'''Σατάν (nom propre) (m)''' : Satan.<br>
'''Σδεύς, -έως (nom propre) (f)''' : Autre forme éolienne de ''Ζεύς''.<br>
'''Σεϐάστεια, -ίας (nom propre) (f)''' : Sivas.<br>
'''Σεϐαστή, -ῆς (nom propre) (f)''' : .<br>
'''Σεϐαστός, -οῦ (nom propre) (m)''' : Sébastien.<br>
'''Σειρήν, -ῆνος (nom propre) (f)''' : sirène.<br>
'''Σείριος, -ίου (nom propre) (m)''' : Sirius.<br>
'''Σελεύκεια, -ίας (nom propre) (f)''' : Séleucie.<br>
'''Σελεύκειος, -α, -ον (adjectif)''' : séleucien.<br>
'''Σελευκεύς, -έως (nom commun) (m)''' : Séleucien.<br>
'''Σελευκίδης, -ου (nom commun) (m)''' : Séleucide.<br>
'''Σελευκίς, -δος (nom commun) (f)''' : Séleucienne.<br>
'''Σέλευκος, -ύκου (nom propre) (m)''' : Séleuce.<br>
'''Σελήνη, -ης (nom propre) (f)''' : [[wikt:Séléné|Séléné]].<br>
'''Σεντικλῆς, -έους (nom propre) (m)''' : Senticlès.<br>
'''Σέργιος, -ίου (nom propre) (m)''' : Serge.<br>
'''Σέσορθος, -όρθου (nom commun) (m)''' : Djéser.<br>
'''Σεύθης, -ου (nom commun) (m)''' : Seuthès.<br>
'''Σευθόπολις, -όλεως (nom propre) (f)''' : .<br>
'''Σῆθ (nom propre) (m)''' : Seth.<br>
'''Σηρική, -ῆς (nom propre) (f)''' : Chine.<br>
'''Σθεννώ, -οῦς (nom propre) (f)''' : Sthéno.<br>
'''Σίϐυλλα, -ύλλης (nom propre) (f)''' : Sibylle.<br>
'''Σιδών, -ῶνος (nom propre) (f)''' : Sidon.<br>
'''Σικελία, -ας (nom propre) (f)''' : Sicile.<br>
'''Σικελιώτης, -ου (nom commun) (m)''' : Sicéliote.<br>
'''Σικελιῶτις, -ώτιδος (nom commun) (f)''' : Sicéliotide.<br>
'''Σίκελος, -έλου (nom commun) (m)''' : Sicule.<br>
'''Σικυών, -ῶνος (nom propre) (f)''' : Sicyone.<br>
'''Σίσυφος, -ύφου (nom propre) (m)''' : Sisyphe.<br>
'''Σίνη, -ης (nom propre) (f)''' : Chine.<br>
'''Σíνων, -ος (nom propre) (m)''' : Sinon. (cousin d'Ulysse)<br>
'''Σιός, -οῦ (nom propre) (m)''' : Autre forme béotienne de ''Ζεύς''.<br>
'''Σίσυφος, -ύφου (nom propre) (m)''' : Sisyphe.<br>
'''Σκορπιός, -οῦ (nom commun) (m)''' : Scorpion.<br>
'''Σκύθαινα, -ίνης (nom propre) (f)''' : Scythe.<br>
'''Σκύθης, -ου (nom propre) (m)''' : Scythe.<br>
'''Σκυθία, -ας (nom propre) (f)''' : Scythie.<br>
'''Σκύλλα, -ης (nom propre) (f)''' : Scylla.<br>
'''Σκύλλη, -ης (nom propre) (f)''' : Forme homérique de ''Σκύλλα''.<br>
'''Σόλλαξ, -κος (nom propre) (m)''' : Sollax (ancien nom du Tigre).<br>
'''Σολομών, -ος (nom propre) (m)''' : Salomon.<br>
'''Σόλων, -ος (nom propre) (m)''' : Solon. (cousin d’Ulysse)<br>
'''Σούηϐος, -ήϐου (nom propre) (m)''' : Suève.<br>
'''Σουοϐηνός, -οῦ (nom commun) (m)''' : Slave.<br>
'''Σουσάννα, -ας (nom propre) (f)''' : Suzanne.<br>
'''Σοῦφις, -ύφιδος (nom propre) (m)''' : Souphis.<br>
'''Σοῦχος, -ύχου (nom propre) (m)''' : Sobek.<br>
'''Σοφία, -ας (nom propre) (f)''' : Sophie.<br>
'''Σοφοκλῆς, -έους (nom propre) (m)''' : Sophocle.<br>
'''Σπάρτα, -ας (nom propre) (f)''' : Forme dorienne de ''Σπάρτη''.<br>
'''Σπάρτη, -ης (nom propre) (f)''' : Sparte.<br>
'''Σπαρτιάτης, -ου (nom commun) (m)''' : Spartiate.<br>
'''Σπαρτιᾶτις, -άτιδος (nom commun) (f)''' : Spartiate.<br>
'''Σταυανός, -οῦ (nom commun) (m)''' : Slave.<br>
'''Στέφανος, -άνου (nom propre) (m)''' : Étienne ; Stéphane.<br>
'''Στράϐων, -ος (nom propre) (m)''' : Strabon.<br>
'''Στρογγύλη, ης (nom propre) (f)''' : Stromboli. (île)<br>
'''Στρούθας, -ου (nom propre) (m)''' : Struthas.<br>
'''Στύξ, -γός (nom propre) (f)''' : Styx.<br>
'''Σύϐαρις, -άριδος (nom propre) (f)''' : Sybaris (ville).<br>
'''Σύϐαρις, -άρεως (nom propre) (m)''' : Sybaris (fleuve).<br>
'''Συδύκ (nom propre) (m)''' : Sydyk.<br>
'''Συρία, -ας (nom propre) (f)''' : Syrie.<br>
'''Συριακός, -ός, -όν (adjectif)''' : syrien.<br>
'''Σύριος, -ίου (nom commun) (m)''' : Syrien.<br>
'''Σῦριγξ, -ύριγγος (nom propre) (f)''' : Syrinx.<br>
'''Σφίγξ, -γός (nom propre) (f)''' : Sphinx, Sphinge.<br>
'''Σωσίας, -ου (nom propre) (m)''' : Sosie.<br>
'''Σωσίπατρος, -άτρου (nom propre) (m)''' : Sosipatros.<br>
==Τ==
'''τάγηνον, -ήνου (nom commun) (n)''' : Forme dorienne de ''τήγανον''.<br>
'''τάγμα, -τος (nom commun) (n)''' : Arrangement. (Militaire) Régiment.<br>
'''ταινία, -ας (nom commun) (f)''' : ruban.<br>
'''ταινίη, -ης (nom commun) (f)''' : Forme ionienne de ''ταινία''.<br>
'''τακτική, -ῆς (nom commun) (f)''' : tactique.<br>
'''τακτικός, -ή, -όν (adjectif)''' : tactique.<br>
'''τακτικῶς (adverbe)''' : tactiquement.<br>
'''τακτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τακτικός''.<br>
'''τακ τικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τακτικός''.<br>
'''τακτικώτατα, -, - (adverbe)''' : Superlatif de ''τακτικῶς''.<br>
'''τακτικώτερον, -, - (adverbe)''' : Comparatif de ''τακτικῶς''.<br>
'''τάλαντον, -άντου (nom commun) (n)''' : plateau de balance.<br>
'''ταλαντοῦμαι (verbe)''' : osciller.<br>
'''ταλάντωσις, -ώσεως (nom commun) (f)''' : oscillation.<br>
'''ταμεῖον, -ίου (nom commun) (n)''' : .<br>
'''ταμία, -ας (nom commun) (f)''' : maîtresse de maison.<br>
'''ταμιακόν, -οῦ (nom commun) (m)''' : fisc.<br>
'''ταμιακός, -ή, -όν (adjectif)''' : fiscal.<br>
'''ταμιακότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ταμιακός''.<br>
'''ταμιακότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ταμιακός''.<br>
'''ταμιακῶς (adverbe)''' : fiscalement.<br>
'''ταμίας, -ου (nom commun) (m)''' : Dispensateur, distributeur ; partageur. Intendant, économe. Gardien d’un trésor. Directeur, ordonnateur ; arbitre.<br>
'''ταμιεῖον, -ίου (nom commun) (n)''' : caisse.<br>
'''ταμιεύω (verbe)''' : .<br>
'''τάμνω (verbe)''' : Forme homérique et ionienne de ''τέμνω''.<br>
'''τάξις, -εως (nom commun) (f)''' : disposition.<br>
'''τάξος, -ου (nom commun) (f)''' : if.<br>
'''τάπης, -τος (nom commun) (m)''' : tapis.<br>
'''τάραγμα, -άγματος (nom commun) (m)''' : inquiétude ; trouble.<br>
'''ταραξίας, -ου (nom commun) (m)''' : trouble-fête.<br>
'''τάραξις, -άξεως (nom commun) (f)''' : trouble.<br>
'''ταράσσω (verbe)''' : troubler.<br>
'''ταραχή, -ῆς (nom commun) (f)''' : trouble.<br>
'''ταρταροῦχος, -ος, -ον (adjectif)''' : du Tartare.<br>
'''τάσις, -εως (nom commun) (f)''' : tension.<br>
'''τάσσω (verbe)''' : disposer.<br>
'''τατᾶ (interjection)''' : papa ; maman.<br>
'''ταταλίζω (verbe)''' : .<br>
'''ταῦρος, -ύρου (nom commun) (m)''' : taureau.<br>
'''ταυρῶ (verbe)''' : tirer.<br>
'''ταῦ (nom commun) (n)''' : tau.<br>
'''τάφος, -ου (nom commun) (m)''' : tombeau.<br>
'''τάχα (adverbe)''' (Devient ''τάχ’'' devant un mot commençant par une voyelle à esprit doux.) : bientôt.<br>
'''ταχέως (adverbe)''' : rapidement.<br>
'''ταχύς, -εῖα, -ύ (adjectif)''' : rapide ; pressé.<br>
'''ταχύτατος, -άτη, -ύτατον (adjectif)''' : Superlatif de ''ταχύς''.<br>
'''ταχύτερος, -έρα, -ύτερον (adjectif)''' : Comparatif de ''ταχύς''.<br>
'''ταχυτής, -ῆτος (nom commun) (f)''' : rapidité.<br>
'''ταώς, -ώ (nom commun) (m)''' : paon.<br>
'''τείνω (verbe)''' : étirer ; tendre.<br>
'''τέκτων, -ονος (nom commun) (m)''' : Auteur, créateur. Ouvrier, artisan.<br>
'''τεῖχος, -ίχους (nom commun) (n)''' : mur de ville.<br>
'''τέλειος, -ία, -ιον (adjectif)''' : terminé ; parfait.<br>
'''τελειότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''τέλειος''.<br>
'''τελειότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''τέλειος''.<br>
'''τελείως (adverbe)''' : parfaitement.<br>
'''τελειῶ (verbe)''' : terminer.<br>
'''τέλεσμα, -έσματος (nom commun) (n)''' : Paiement, taxe. Certificat.<br>
'''τελεστιχίς, -δος (nom commun) (f)''' : téléstiche.<br>
'''τελετή, -ῆς (nom commun) (f)''' : cérémonie.<br>
'''τελευταῖος, -ία, -ῖον (adjectif)''' : dernier.<br>
'''τελευτή, -ῆς (nom commun) (f)''' : fin ; finalité.<br>
'''τελέω (verbe)''' : Accomplir. Mettre un terme à.<br>
'''τέλος, -ους (nom commun) (n)''' : Achèvement ; accomplissement ; réalisation. Prix dans les luttes.<br>
'''τέμαχος, -άχους (nom commun) (n)''' : tranche ; morceau.<br>
'''τέμενος, -ένους (nom commun) (n)''' : téménos.<br>
'''τέμνω (verbe)''' : couper.<br>
'''τέρας, -τος (nom commun) (n)''' : Signe divin ; monstre.<br>
'''τεράστιος -α -ον (adjectif)''' : monstrueux.<br>
'''τερπνός, -ή, -όν (adjectif)''' : Amusant ; plaisant.<br>
'''τερπνότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''τερπνός''.<br>
'''τερπνότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''τερπνός''.<br>
'''τερπνότης, -τος (nom commun) (f)''' : amusement.<br>
'''τερπνῶς (adverbe)''' : plaisamment.<br>
'''τέρπω (adverbe)''' : Prendre plaisir ; s’amuser.<br>
'''τέσσαρες (adjectif numéral)''' : quatre.<br>
'''τετράδιον, -ίου (nom commun) (n)''' : cahier.<br>
'''τετραίνω (verbe)''' : trouer.<br>
'''τετράλημμα, -ήμματος (nom commun) (n)''' : tétralemme.<br>
'''τετράπους, -δος (nom commun) (m)''' : quadrupède.<br>
'''τετράς, -δος (nom commun) (f)''' : .<br>
'''τετράων, -ος (nom commun) (m)''' : coq de bruyère.<br>
'''τετράστιχον, -ίχου (nom commun) (n)''' : quatrain.<br>
'''τετταράκοντα (adjectif numéral)''' quarante.<br>
'''τέττιξ, -γος (nom commun) (m)''' : cigale.<br>
'''τεῦτλον, -ύτλου (nom commun) (n)'''' : blette.<br>
'''τεῦχος, -ύχους (nom commun) (n)''' : Ustensile, instrument. (Au pluriel) Armes, armure. (Au pluriel) Agrès de navire (voiles, cordages, rames). Urne pour les libations. Urne funéraire. Baignoire. Tonneau de bois. Huche pour la farine. Ruche d’abeilles.(Par analogie) Vaisseau du corps. Enveloppe qui enferme les petits. Livre.<br>
'''τεύχω (verbe)''' : .<br>
'''τέφρα, -ας (nom commun) (f)''' : cendre.<br>
'''τέφρη, -ης (nom commun) (f)''' : Forme homérique et ionienne de ''τέφρα''.<br>
'''τεχνάζω (verbe)''' : faire avec art.<br>
'''τέχνασμα, -άσματος (nom commun) (n)'''' : Artifice, machination, ruse.<br>
'''τέχνη, -ης (nom commun) (f)''' : Art ; habileté.<br>
'''τέχνημα, -ήματος (nom commun) (n)''' : artéfact.<br>
'''τεχνητός, -ή, -όν (adjectif)''' : artificiel.<br>
'''τεχνητῶς (adverbe)''' : artificiellement.<br>
'''τεχνητώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τεχνητός''.<br>
'''τεχνητώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τεχνητός''.<br>
'''τεχνικός, -ή, -όν (adjectif)''' : artistique.<br>
'''τεχνικῶς (adverbe)''' : artistiquement.<br>
'''τεχνικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τεχνικός''.<br>
'''τεχνικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τεχνικός''.<br>
'''τεχνῶμαι (verbe)''' : .<br>
'''τηγάνιον, -ίου (nom commun) (n)''' : Diminutif de ''τήγανον''.<br>
'''τήγανον, -άνου (nom commun) (n)''' : poêle à frire.<br>
'''τήκω (verbe)''' : fondre.<br>
'''τῆλε (adverbe ; préposition)''' Loin, au loin. (Avec le génitif) Loin de.<br>
'''τήμερον (adverbe)''' : Forme attique de ''σήμερον''.<br>
'''-τήρ, -ῆρος (suffixe) (m)''' : Suffixe nominal.<br>
'''-τής, -ῆτος (suffixe) (f)''' : Suffixe permettant de créer à partir d’un adjectif le nom désignant la qualité correspondante.<br>
'''-της, -τος (suffixe) (f)''' : Suffixe de même usage que ''-τής''.<br>
'''τίγρις, -εως (nom commun) (m/f)''' : Tigre ; tigresse.<br>
'''τίθημι (verbe)''' : Poser ; placer.<br>
'''τιθήνη, -ης (nom commun) (f)''' : nourrice.<br>
'''τεκμηρίωσις, -ώσεως (nom commun) (f)''' : documentation.<br>
'''τεκμηριῶ (verbe)''' : documenter.<br>
'''τίκτω (verbe)''' : Engendrer, produire ; mettre au monde.<br>
'''τιμή, -ῆς (nom commun) (f)''' : (Sens positif) Évaluation, estimation. Prix attaché à un honneur. Ce qui est tenu en honneur ; objet de l’estime, du respect ; autorité, magistrature. (Sens négatif) Peine, châtiment, vengeance.<br>
'''τίμιος, -α, -ον (adjectif)''' : honnête.<br>
'''τιμιότης, -τος (nom commun) (f)''' : honnêteté.<br>
'''τιμωρητέος, -α, -ον (adjectif)''' : punissable.<br>
'''τιμωρητικός, -ή, -όν (adjectif)''' : punitif.<br>
'''τιμωρητικῶς (adverbe)''' : punitivement.<br>
'''τιμωρητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τιμωρητικός''.<br>
'''τιμωρητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τιμωρητικός''.<br>
'''τιμωρητικώτατα, -, - (adverbe)''' : Superlatif de ''τιμωρητικῶς''.<br>
'''τιμωρητικώτερον, -, - (adverbe)''' : Comparatif de ''τιμωρητικῶς''.<br>
'''τιμωρία, -ας (nom commun) (f)''' : punition.<br>
'''τιμωρῶ (verbe)''' : punir.<br>
'''τιμῶ (verbe)''' : honorer.<br>
'''τίνω (verbe)''' : Payer ; payer pour ses fautes, expier.<br>
'''τιούχα, -ας (nom commun) (f)''' : Autre forme béotienne de ''τύχη''.<br>
'''τίσις, -εως (nom commun) (f)''' : Paiement, récompense. Pénalité, vengeance.<br>
'''τιταίνω (verbe)''' : étendre.<br>
'''τίταξ, -κos (nom commun) (m)''' : roi.<br>
'''τίτας, -αντος (nom commun) (m)''' : vengeur.<br>
'''τίτης, -ου (nom commun) (m)''' : Forme dorienne de ''τίτας''.<br>
'''τῖφος, -ίφου (nom commun) (m)''' : Étang, marais.<br>
'''τίω (verbe)''' : rendre (i.e. payer) des hommages à quelqu’un, estimer une personne. Évaluer, estimer.<br>
'''τμῆμα, -ήµατος (nom commun) (n)''' : Partie, secteur ; section.<br>
'''τμῆσις, -ήσεως (nom commun) (f)''' : césure.<br>
'''τοιοῦτος, -αύτη, -οῦτο (pronom)''' : .<br>
'''τοῖχος, -ίχου (nom commun) (m)''' : Mur de maison ; bord ou paroi d’un navire.<br>
'''τοιχωρύχημα, -ήµατος (nom commun) (n)''' : cambriolage.<br>
'''τοιχώρυχος, -ύχου (nom commun) (m)''' : cambrioleur.<br>
'''τοιχωρυχῶ (verbe)''' : cambrioler.<br>
'''τοκογλυφία, -ας (nom commun) (f)''' : usure.<br>
'''τοκογλυφικός, -ή, -όν (adjectif)''' : usurier.<br>
'''τοκογλύφος, -ου (nom commun) (m)''' : usurier.<br>
'''τόκος, -ου (nom commun) (m)''' : Parturition, accouchement. Descendance. Intérêt de l'argent prêté.<br>
'''τόλμα, -ης (nom commun) (f)''' : Audace ; courage.<br>
'''τολμηρός, -ή, -όν (adjectif)''' : hardi.<br>
'''τολμηρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''τολμηρός''.<br>
'''τολμηρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''τολμηρός''.<br>
'''τολμηρότης, -τος (nom commun) (f)''' : hardiesse.<br>
'''τολμηρῶς (adverbe)''' : hardiment.<br>
'''τολμηρώτατα, -, - (adverbe)''' : Superlatif de ''τολμηρῶς''.<br>
'''τολμηρώτερον, -, - (adverbe)''' : Comparatif de ''τολμηρῶς''.<br>
'''τολμῶ (verbe)''' : oser.<br>
'''-τομία, -ας (suffixe) (f)''' : Suffixe signifiant « coupure », « césure ».<br>
'''τόμος, -ου (nom commun) (m)''' : Tranche, pièce ; chose coupée.<br>
'''τομός, -οῦ (nom commun) (m)''' : Coupure, action de couper.<br>
'''τοπικός, -ή, -όν (adjectif)''' : local.<br>
'''τοπικῶς (adverbe)''' : localement.<br>
'''τοπικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τοπικός''.<br>
'''τοπικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τοπικός''.<br>
'''τοπικώτατα, -, - (adverbe)''' : Superlatif de ''τοπικῶς''.<br>
'''τοπικώτερον, -, - (adverbe)''' : Comparatif de ''τοπικῶς''.<br>
'''τοξάριον, -ίου (nom commun) (n)''' : archet.<br>
'''τοξικός, -ή, -όν (adjectif)''' : qui convient pour un arc ou pour des flèches.<br>
'''τόξον, -ου (nom commun) (n)''' : arc ; arc-en-ciel.<br>
'''τοξότης, -ου (nom commun) (m)''' : archer.<br>
'''τὸ (article défini)''' : le (neutre).<br>
'''τούχα, -ας (nom commun) (f)''' : Forme béotienne de ''τύχη''.<br>
'''τράγημα, -ατος (nom commun) (n)''' : friandise.<br>
'''τραγικός, -ή, -όν (adjectif)''' : tragique.<br>
'''τραγικότατα, -, - (adverbe)''' : Superlatif de ''τραγικῶς''.<br>
'''τραγικότερον, -, - (adverbe)''' : Comparatif de ''τραγικῶς''.<br>
'''τραγικῶς (adverbe)''' : comiquement.<br>
'''τραγικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τραγικός''.<br>
'''τραγικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τραγικός''.<br>
'''τραγίσκος, -ου (nom commun) (m)''' : chevreau.<br>
'''τράγος, -ου (nom commun) (m)''' : bouc.<br>
'''τραγῳδία, -ας (nom commun) (f)''' : tragédie.<br>
'''τραγωδῶ (verbe)''' : chanter.<br>
'''τράπεζα, -ης (nom commun) (f)''' : table.<br>
'''τραυλίζω (verbe)''' : bégayer, bafouiller.<br>
'''τραυλισμός, -οῦ (nom commun) (m)''' : bégaiement, bafouillis.<br>
'''τραυλός, -οῦ (nom commun) (m)''' : bègue, bafouilleur.<br>
'''τραῦμα, -ύματος (nom commun) (n)''' : Blessure. Déroute, désastre.<br>
'''τράχηλος, -ήλου (nom commun) (m)''' : Cou. (En particulier) Derrière du cou, nuque.<br>
'''τραχύς, -εῖα, -ύ (adjectif)''' : rude.<br>
'''τράχω (verbe)''' : Forme dorienne de ''τρέχω''.<br>
'''τρεῖς (adjectif numéral)''' : trois.<br>
'''τρέμω (verbe)''' : trembler, s’agiter ; s’ébranler.<br>
'''τρέπω (verbe)''' : tourner.<br>
'''τρέστης, -ου (nom commun) (m)''' : couard ; peureux.<br>
'''τρέφω (verbe)''' : Rendre compact. Rendre gras, engraisser, nourrir. Nourrir, élever. (Par extension) Élever, former, façonner, instruire. Pourvoir aux besoins de. S’épaissir, se condenser. Être nourri ou élevé.<br>
'''τρέχω (verbe)''' : courir.<br>
'''τρέω (verbe)''' : avoir peur.<br>
'''τρῆμα, -ήµατος (nom commun) (n)''' : trou.<br>
'''τρηρός, -ή, -όν (adjectif)''' : fou.<br>
'''τρήρων, -ος (nom commun) (m/f)''' : timide.<br>
'''τριάκοντα (adjectif numéral)''' : trente.<br>
'''τρίϐω (verbe)''' : frotter.<br>
'''τρίλημμα, -ήμματος (nom commun) (n)''' : trilemme.<br>
'''τρίμμα, -τος (nom commun) (n)''' : .<br>
'''τρίπος, -ου (nom commun) (m)''' : trépied.<br>
'''τρίπους, -δος (adjectif)''' : tripède.<br>
'''τρισκελής, -οῦ (nom commun) (m)''' : triskèle.<br>
'''τρισκέλιον, -ίου (nom commun) (n)''' : Diminutif de ''τρισκελής''.<br>
'''τριταγωνιστής, -οῦ (nom commun) (m)''' : tritagoniste.<br>
'''τρίτος, -η, -ον (adjectif numéral)''' : troisième.<br>
'''τρίφυλλον, -ύλλου (nom commun) (n)''' : trèfle.<br>
'''-τρον, -ου (suffixe) (n)''' : Suffixe servant à former des noms d’instruments.<br>
'''τρόπαιον, -ίου (nom commun) (n)''' : trophée.<br>
'''τροπή, -ῆς (nom commun) (f)''' : Tour. Fuite. Révolution, changement. (Rhétorique) Tournure de phrase.<br>
'''τροπικός, -ή, -όν (adjectif)''' : tournant.<br>
'''τρόπος, -ου (nom commun) (m)''' : Tour, direction ; façon, mode, manière.<br>
'''τροχαῖος, -ίου (nom commun) (m)''' : trochée.<br>
'''τροχαλία, -ας (nom commun) (f)''' : poulie.<br>
'''τροχίλος, -ου (nom commun) (m)''' : poulie.<br>
'''τρόχος, -ου (nom commun) (m)''' : blaireau.<br>
'''τροχός, -οῦ (nom commun) (m)''' : Roue. Tour de potier. Pain (de forme ronde) de suif ou de cire.<br>
'''τρύϐλιον, -ίου (nom commun) (n)''' : bol ; coupe.<br>
'''τρυγόνιον, -ίου (nom commun) (n)''' : Diminutif de ''τρυγών ''.<br>
'''τρυγών, -όνος (nom commun) (f)''' : tourterelle.<br>
'''τρύζω (verbe)''' : .<br>
'''τρῦπα, -ύπας (nom commun) (f)''' : trou.<br>
'''τρύπημα, -ήματος (nom commun) (n)''' : trou.<br>
'''τρύω (verbe)''' : user.<br>
'''τύρρις, -ος (nom commun) (f)''' : variante de ''τύρσις''.<br>
'''τύρσις, -ος (nom commun) (f)''' : tour (construction élevée).<br>
'''τρύφαξ, -κος (nom commun) (m)''' : débauché.<br>
'''τρυφερός, -ά, -όν (adjectif)''' : délicat.<br>
'''τρυφερότης, -τος (nom commun) (f)''' : délicatesse.<br>
'''τρυφερώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τρυφερός''.<br>
'''τρυφερώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τρυφερός''.<br>
'''τρυφερῶς (adverbe)''' : délicatement.<br>
'''τρυφή, -ῆς (nom commun) (f)''' : Douceur, mollesse. Luxe, délicatesse. Dissolution, débauche.<br>
'''τρύφημα, -ήματος (nom commun) (n)''' : orgueil.<br>
'''τρυφητής, -ής, -ές (adjectif)''' : voluptueux.<br>
'''τρυφῶ (verbe)''' : être extravagant, se donner des airs.<br>
'''τρῦχος, -ύχους (nom commun) (n)''' : Haillon, lambeau.<br>
'''τρύχω (verbe)''' : épuiser, user.<br>
'''τρώγω (verbe)''' : ronger, grignoter.<br>
'''τρωϊκός, -ή -όν (adjectif)''' : troyen.<br>
'''τρῶ (verbe)''' : avoir peur.<br>
'''τῦκον, -ύκου (nom commun) (m)''' : Forme béotienne de ''σῦκον''.<br>
'''τύμϐος, -ου (nom commun) (m)''' : tumulus.<br>
'''τυνδάρειος, -ος, -ον (nom commun) (m)''' : tyndaréen.<br>
'''τυπικός, -ή -όν (adjectif)''' : figuré.<br>
'''τυπικῶς (adverbe)''' : figurativement.<br>
'''τυπικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''τυπικός''.<br>
'''τυπικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''τυπικός''.<br>
'''τυπικώτατα, -, - (adverbe)''' : Superlatif de ''τυπικῶς''.<br>
'''τυπικώτερον, -, - (adverbe)''' : Comparatif de ''τυπικῶς''.<br>
'''τύπος, -ου (nom commun) (m)''' : Coup ; frappe. Frappe, marque du coup ; sceau, impression.<br>
'''τύπτω (verbe)''' : frapper.<br>
'''τύραννος, -άννου (nom commun) (m)''' : Maître, dominateur. (Péjoratif) Tyran, dictateur ; despote.<br>
'''τύρϐη, -ης (nom commun) (f)''' : tumulte, désordre.<br>
'''τυρός, -οῦ (nom commun) (m)''' : fromage.<br>
'''τυτώ, -οῦς (nom commun) (f)''' : chouette.<br>
'''τύ (pronom personnel)''' : Forme dorienne de ''σύ''.<br>
'''τυφλός, -ή -όν (adjectif)''' : aveugle.<br>
'''τῦφος, -ύφου (nom commun) (m)''' : fumée, vapeur qui monte au cerveau ; orgueil.<br>
'''τῦφω (verbe)''' : enfumer.<br>
'''τυχαῖος, -ία, -ῖον (adjectif)''' : chanceux.<br>
'''τυχερῶς (adverbe)''' : .<br>
'''τύχη, -ης (nom commun) (f)''' : chance.<br>
'''τυχηρός, -ά, -όν, (adjectif)''' : chanceux.<br>
'''τύψις, -εως (nom commun) (f)''' : remords.<br>
'''Ταλθύϐιος, -ίου (nom propre) (m)''' : Talthybios.<br>
'''Τάλως, -ώ (nom propre) (m)''' : Talos.<br>
'''Τάμεσις, -έσεως (nom propre) (f)''' : Tamise.<br>
'''Τάναϊς, -άϊδος (nom propre) (m)''' : Tanaïs.<br>
'''Τάν, -ός (nom propre) (m)''' : Forme crétoise de ''Ζεύς''.<br>
'''Ταραντῖνος, -ίνου (nom commun) (m)''' : Tarentin.<br>
'''Τάρας, -αντος (nom propre) (m)''' : Tarente.<br>
'''Τάρταρος, -άρου (nom propre) (m)''' : Tartare.<br>
'''Ταῦρος, -ύρου (nom propre) (m)''' : Taureau.<br>
'''Τειρεσίας, -ου (nom propre) (m)''' : Tirésias.<br>
'''Τεΐσπης, -ου (nom propre) (m)''' : Teispès.<br>
'''Τελεύτας, -αντος (nom propre) (m)''' : Téleutas.<br>
'''Τελευτίας, -ου (nom propre) (m)''' : Téleutias.<br>
'''Τερψιχόρα, -ας (nom propre) (f)''' : Terpsichore.<br>
'''Τεύτων, -ονος (nom commun) (m)''' : Teuton.<br>
'''Τηθύς, -ος (nom propre) (f)''' : [[wikt:Téthys|Téthys]].<br>
'''Τηλέγονος, -όνου (nom propre) (m)''' : Télégonos.<br>
'''Τηλέμαχος, -άχου (nom propre) (m)''' : Télémaque.<br>
'''Τηρεύς, -έως (nom propre) (m)''' : Térée.<br>
'''Τίγρης, -τος (nom propre) (m)''' : Tigre (fleuve du Moyen-Orient).<br>
'''Τίγρις, -δος (nom propre) (f)''' : Forme alternative de ''Τίγρης''.<br>
'''Τιθωνός, -οῦ (nom propre) (m)''' : Tithon.<br>
'''Τιμασίθεος, -έου (nom propre) (m)''' : Timasithée.<br>
'''Τιμόθεος, -έου (nom propre) (m)''' : Timothée.<br>
'''Τίμων, -ωνος (nom propre) (m)''' : Timon.<br>
'''Τισαμενός, -οῦ (nom propre) (m)''' : Tisamène.<br>
'''Τίσανδρος, -άνδρου (nom propre) (m)''' : Tisandre.<br>
'''Τισίας, -ου (nom propre) (m)''' : Tisias.<br>
'''Τισικράτης, -ου (nom propre) (m)''' : Tisicrate.<br>
'''Τισιφόνη, -ης (nom propre) (f)''' : Tisiphone (une des Érynies).<br>
'''Τισσαφέρνης, -ου (nom propre) (m)''' : Tissapherne.<br>
'''Τιτάν, -ᾶνος (nom propre) (m)''' : Titan.<br>
'''Τιτανίς, -δος (nom propre) (f)''' : Titanide.<br>
'''Τιτυός, -οῦ (nom propre) (m)''' : Tityos.<br>
'''Τόσορθρος, -όρθου (nom commun) (m)''' : Djéser.<br>
'''Τοξότης, -ου (nom propre) (m)''' : Sagittaire.<br>
'''Τουρκία, -ας (nom propre) (f)''' : Turquie.<br>
'''Τοῦρκος, -ύρκου (nom propre) (m)''' : Turc.<br>
'''Τρίπολις, -όλεως (nom propre) (f)''' : Tripoli.<br>
'''Τρίτων, -ος (nom propre) (m)''' : Triton.<br>
'''Τροία, -ας (nom propre) (f)''' : Troie.<br>
'''Τρωάς, -δος (nom commun) (f)''' : Troyenne.<br>
'''Τρώς, -ός (nom commun) (m)''' : Troyen.<br>
'''Τῦϐι (nom propre) (m)''' : Tybi.<br>
'''Τυδεύς, -έως (nom propre) (m)''' : Tydée.<br>
'''Τυνδάρεως, -εω (nom propre) (m)''' : Tyndare.<br>
'''Τυνδαρίδης, -ου (nom propre) (m)''' : Tyndaride.<br>
'''Τυνδαρίς, -δος (nom propre) (f)''' : Tyndaride.<br>
'''Τυφάων, -ος (nom propre) (m)''' : Forme de ''Τυφῶν''.<br>
'''Τυφωεύς, -έως (nom propre) (m)''' : Forme de ''Τυφῶν''.<br>
'''Τυφώς, - (nom propre) (m)''' : Forme de ''Τυφῶν''.<br>
'''Τυφῶν, -ος (nom propre) (m)''' : Typhon.<br>
'''Τύχη, -ης (nom propre) (f)''' : [[wikt:Tyché|Tyché]].<br>
'''Τύχων, -ος (nom propre) (m)''' : Tychon.<br>
==Υ==
'''ὕαινα, -ίνης (nom commun) (f)''' : hyène.<br>
'''ὕαλος, -άλου (nom commun) (m)''' : verre. (matière)<br>
'''ὑϐριζω (verbe)''' : maltraiter, outrager.<br>
'''ὕϐρις, -εως (nom commun) (f)''' : démesure, violence ; excès, outrage.<br>
'''ὑγίεια, -ίας (nom commun) (f)''' : propreté.<br>
'''ὑγιεινή, -ῆς (nom commun) (f)''' : santé.<br>
'''ὑγιεινός, -ή, -όν (adjectif)''' : salubre.<br>
'''ὑγραίνω (verbe)''' : humidifier.<br>
'''ὑγρασία, -ας (nom commun) (f)''' : humidité.<br>
'''ὑγρός, -ά, -όν (adjectif)''' : humide.<br>
'''ὑγρότατα, -, - (adverbe)''' : Superlatif de ''ὑγρῶς''.<br>
'''ὑγρότερον, -, - (adverbe)''' : Comparatif de ''ὑγρῶς''.<br>
'''ὑγρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ὑγρός''.<br>
'''ὑγρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''ὑγρός''.<br>
'''ὑγρῶς (adverbe)''' : humidement.<br>
'''ὑδραγωγεῖον, -ίου (nom commun) (n)''' : aqueduc.<br>
'''ὑδράργυρος, -ύρου (nom commun) (m)''' : mercure.<br>
'''ὑδρόμελι, -τος (nom commun) (n)''' : Boisson à base d'eau et de miel.<br>
'''ὑδρο- (préfixe)''' : hydro-.<br>
'''ὕδωρ, -ατος (nom commun) (n)''' : Eau ; sueur.<br>
'''ὕενος, -ένου (nom commun) (m)''' : mercure.<br>
'''υἱός, -οῦ (nom commun) (m)''' : fils.<br>
'''ὕλη, -ης (nom commun) (f)''' : matière ; bois.<br>
'''ὑμεῖς (pronom personnel)''' : vous.<br>
'''ὑμέτερος, -έρα, -έτερον (adjectif possessif)''' : votre.<br>
'''ὑμήν, -ένος (nom commun) (m)''' : Membrane ; pellicule enveloppant les organes du corps.<br>
'''ὕμνος, -ου (nom commun) (m)''' : trame ; chant.<br>
'''ὑμνῳδία, -ας (nom commun) (f)''' : hymne.<br>
'''ὑμνῶ (verbe)''' : .<br>
'''ὔμοι (adverbe)''' : Forme éolienne de ''ὁμοῦ''.<br>
'''ὔμοιος, -, - (adjectif)''' : Forme éolienne de ''ὅμοιος''.<br>
'''ὑμοῖος, -, -ῖον (adjectif)''' : Forme arcado-chypriote de ''ὅμοιος''.<br>
'''ὑπαγόρευσις, -ύσεως (nom commun) (f)''' : dictée.<br>
'''ὑπάγω (verbe)''' : mener sous.<br>
'''ὑπακτικόν -οῦ (nom commun) (n)''' : laxatif.<br>
'''ὑπακτικός -ή -όν (adjectif)''' : laxatif.<br>
'''ὑπακοή, -ῆς (nom commun) (f)''' : obéissance.<br>
'''ὑπάκουος, -η, -ον (adjectif)''' : obéissant.<br>
'''ὑπακούω (verbe)''' : obéir.<br>
'''ὑπαρξιακός, -ή, -όν (adjectif)''' : existentiel.<br>
'''ὕπαρξις, -άρξεως (nom commun) (f)''' : existence.<br>
'''ὑπάρχω (verbe)''' : exister.<br>
'''ὑπεξαίρεσις, -έσεως (nom commun) (f)''' : malversation.<br>
'''ὑπεξαιρῶ (verbe)''' : .<br>
'''ὑπερκόσμιος, -α, -ο (adjectif)''' : .<br>
'''ὑπέρ (adverbe ; préposition)''' : au-dessus.<br>
'''ὑπερϐάλλω (verbe)''' : exagérer.<br>
'''ὑπερϐολή, -ῆς (nom commun) (f)''' : exagération.<br>
'''ὑπερϐολικός, -ή, -όν (adjectif)''' : excessif.<br>
'''ὑπερθετικός, -ή, -όν (adjectif)''' : superlatif.<br>
'''ὑπεριώδης, -ης, -ες (adjectif)''' : ultraviolet.<br>
'''ὑπεροπτικός, -ή, -όν (adjectif)''' : arrogant.<br>
'''ὑπέροπτος, -ος, -ον (adjectif)''' : .<br>
'''ὑπεροψία, -ας (nom commun) (f)''' : arrogance.<br>
'''ὑπέρυθρος, -η, -ον (adjectif)''' : infrarouge.<br>
'''ὑπερφυσικός, -ή, -όν (adjectif)''' : surnaturel.<br>
'''ὑπεύθυνος, -ος, -ον (adjectif)''' : responsable.<br>
'''ὑπευθυνότητα, -ας (nom commun) (f)''' : responsabilité.<br>
'''ὑπηρεσία, -ας (nom commun) (f)''' : service.<br>
'''ὑπηρέτης, -ου (nom commun) (m)''' : serviteur.<br>
'''ὑπηρετικός, -ή, -όν (adjectif)''' : de service.<br>
'''ὑπηρετικῶς (adverbe)''' : -ment.<br>
'''ὑπηρετικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ὑπηρετικός''.<br>
'''ὑπηρετικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ὑπηρετικός''.<br>
'''ὑπηρετικώτατα, -, - (adverbe)''' : Superlatif de ''ὑπηρετικῶς''.<br>
'''ὑπηρετικώτερον, -, - (adverbe)''' : Comparatif de ''ὑπηρετικῶς''.<br>
'''ὑπηρέτρια, -ας (nom commun) (f)''' : servante.<br>
'''ὑπηρετῶ (verbe)''' : servir.<br>
'''ὑπνολαλία, -ας (nom commun) (f)''' : somniloquie.<br>
'''ὑπνολαλῶ (verbe)''' : somniloquer.<br>
'''ὕπνωσις, -ώσεως (nom commun) (f)''' : hypnose.<br>
'''ὑπνωτικός, -ή, -όν (adjectif)''' : hypnotique.<br>
'''ὑπνῶ (adjectif)''' : hypnotiser.<br>
'''ὑπόϐαθρον, -άθρου (nom commun) (n)''' : arrière-plan.<br>
'''ὑπόγειον, -ίου (nom commun) (n)''' : cave.<br>
'''ὑπόδειγμα, -ίγματος (nom commun) (n)''' : exemple.<br>
'''ὑποδειγματικός, -ή, -όν (adjectif)''' : exemplaire.<br>
'''ὑποδείκνυμι (verbe)''' : .<br>
'''ὑπόδημα, -ήματος (nom commun) (n)''' : chaussure.<br>
'''ὑποδηματοποιεῖον, -ίου (nom commun) (n)''' : boutique du cordonnier.<br>
'''ὑποδηματοποιός, -οῦ (nom commun) (m)''' : cordonnier.<br>
'''ὑποζύγιον, -ίου (nom commun) (n)''' : attelage.<br>
'''ὑπόθεσις, -έσεως (nom commun) (f)''' : supposition.<br>
'''ὑποκινῶ (verbe)''' : .<br>
'''ὑποκορίζομαι (verbe)''' : parler comme un enfant.<br>
'''ὑποκόρισμα, -τος (nom commun) (n)''' : sobriquet.<br>
'''ὑποκοριστικός, -ή, -όν (adjectif)''' : caressant, propre à atténuer.<br>
'''ὑποκρίνομαι (verbe)''' : jouer une pièce.<br>
'''ὑπόκρισις, -ίσεως (nom commun) (f)''' : Réponse. Action de jouer un rôle, une pièce, une pantomime. Débit théâtral, déclamation. Feinte, faux-semblant.<br>
'''ὑποκριτής, -οῦ (nom commun) (m)''' : Donneur de réponse. Acteur, comédien.<br>
'''ὑπόκωφος, -η, -ον (adjectif)''' : sourd.<br>
'''ὑποκώφως (adverbe)''' : sourdement.<br>
'''ὑπολογίζομαι (verbe)''' : calculer.<br>
'''ὑπολογισμός, -οῦ (nom commun) (m)''' : calcul.<br>
'''ὑπομιμνήσκω (verbe)''' : .<br>
'''ὑπομονή, -ῆς (nom commun) (f)''' : .<br>
'''ὑπόμνημα, -ήματος (nom commun) (n)''' : .<br>
'''ὑπόνομος, -ου (nom commun) (m)''' : égout.<br>
'''ὑπόστασις, -άσεως (nom commun) (f)''' : .<br>
'''ὑποταγή, -ῆς (nom commun) (f)''' : Subordination, soumission.<br>
'''ὑποτάσσω (verbe)''' : Subordonner, soumettre. Dominer, contrôler.<br>
'''ὑποτίθημι (verbe)''' : supposer.<br>
'''ὑπότριμμα, -ίμματος (nom commun) (n)''' : Sauce aux herbes, sauce verte et piquante.<br>
'''ὑπουργεῖον, -ίου (nom commun) (n)''' : ministère.<br>
'''ὑπουργικός, -ή, -όν (adjectif)''' : ministériel.<br>
'''ὑπουργός, -οῦ (nom commun) (m)''' : ministre.<br>
'''ὑποχώρησις, -ήσεως (nom commun) (f)''' : régression.<br>
'''ὑποχωρητικός, -ή, -όν (adjectif)''' : régressif.<br>
'''ὑποψήφιος, -ίου (nom commun) (m)''' : candidat.<br>
'''ὑπό (adverbe ; préposition)''' : en dessous.<br>
'''ὑπο- (préfixe)''' : relatif au dessous.<br>
'''ὕραξ, -κος (nom commun) (m)''' : souris.<br>
'''ὕσσωπος, -ώπου (nom commun) (f)''' : hysope.<br>
'''ὕστατος, -η, -ον (adjectif)''' : dernier.<br>
'''ὑστερέω (verbe)''' : .<br>
'''ὑστέρησις, -ήσεως (nom commun) (f)''' : hystérèse.<br>
'''ὑστερικός, -ή, -όν (adjectif)''' : utérin ; hystérique.<br>
'''ὑστερο- (préfixe)''' : suivant ; tardif.<br>
'''ὕστερος, -α, -ον (adjectif)''' : postérieur.<br>
'''ὕστερος, -έρου (nom commun) (m)''' : matrice ; utérus.<br>
'''ὑστέρως (adverbe)''' : .<br>
'''ὕστριξ, -χός (nom commun) (m/f)''' : porc-épic.<br>
'''ὗς, -ός (nom commun) (m/f)''' : Porc, sanglier. Truie, laie.<br>
'''ὑφαίνω (verbe)''' : tisser.<br>
'''ὕφαλος, -άλου (nom commun) (m)''' : récif.<br>
'''ὑφή, -ῆς (nom commun) (f)''' : toile d’araignée.<br>
'''ὕφος, -ους (nom commun) (n)''' : tissu.<br>
'''ὑψηλός, -ή, -όν (adjectif)''' : .<br>
'''ὕψι (adverbe)''' : en haut.<br>
'''ὖ ψιλόν (nom commun) (n)''' : upsilon.<br>
'''ὑψίτερος, -έρη, -ερον (adverbe)''' : Comparatif de ''ὕψι''.<br>
'''ὕψιστος, -ίστη, -ιστον (adverbe)''' : Superlatif de ''ὕψι''.<br>
'''ὕψος, -ους (nom commun) (n)''' : hauteur.<br>
'''ὕψωμα, -ώματος (nom commun) (n)''' : élévation.<br>
'''ὕψωσις, -ώσεως (nom commun) (f)''' : élévation.<br>
'''ὑψῶ (verbe)''' : élever.<br>
'''ὕω (verbe)''' : pleuvoir.<br>
'''Ὕϐρις, -εως (nom propre) (f)''' : Hybris.<br>
'''Ὑδροχόος, -ου (nom propre) (m)''' : Verseau.<br>
'''Ὑγίεια, -ίας (nom propre) (f)''' : [[wikt:Hygie|Hygie]].<br>
'''Ὕηττος, -ήττου (nom propre) (m)''' : Hyettos.<br>
'''Ὑκσώς, -οῦς (nom commun) (m)''' : Hyksôs.<br>
'''Ὕλλος, -ου (nom propre) (m)''' : Hyllos.<br>
'''Ὑμέναιος, -ίου (nom propre) (m)''' : [[wikt:Hyménée|Hyménée]].<br>
'''Ὑμήν, -ένος (nom propre) (m)''' : Variante de ''Ὑμέναιος''.<br>
'''Ὑπερίων, -ος (nom propre) (m)''' : Hypérion.<br>
'''Ὕπνος, -ου (nom propre) (m)''' : [[wikt:Hypnos|Hypnos]].<br>
'''Ὑστάσπης, -ου (nom propre) (m)''' : Hystaspès.<br>
'''Ὕψιστος, -ίστου (nom propre) (m)''' : Très-Haut.<br>
==Φ==
'''φαγός, -οῦ (nom commun) (m)''' : Forme dorienne de ''φηγός''.<br>
'''φαιδρός, -ίδρα, -ιδρόν (adjectif)''' : brillant ; rayonnant, enjoué ; gai, jovial.<br>
'''φαίνεσθαι (verbe)''' : se montrer.<br>
'''φαινόμενον, -ένου (nom commun) (n)''' : phénomène.<br>
'''φαίνω (verbe)''' : faire briller.<br>
'''φακός, -οῦ (nom commun) (m)''' : Lentille. (Anatomie) Cristallin. Tache de rousseur.<br>
'''φακόχοιρος, -ίρου (nom commun) (m)''' : phacochère.<br>
'''φάλαγξ, -γος (nom commun) (f)''' : phalange.<br>
'''φάλλαινα, -ίνης (nom commun) (f)''' : baleine.<br>
'''φαλλός, -οῦ (nom commun) (m)''' : phallus.<br>
'''φάμη, -ης (nom commun) (f)''' : Forme dorienne de ''φήμη''.<br>
'''φαμί (verbe)''' : Forme dorienne de ''φημί''.<br>
'''φάναξ, -κος (nom commun) (m)''' : .<br>
'''φανερός, -ά, -όν (adjectif)''' : Apparent. En vue.<br>
'''φανερῶς (adverbe)''' : .<br>
'''φανερώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''φανερός''.<br>
'''φανερώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''φανερός''.<br>
'''φανερῶ (verbe)''' : .<br>
'''φανός, -οῦ (nom commun) (m)''' : .<br>
'''φάνταγμα, -άγματος (nom commun) (n)''' : Forme ionienne de ''φάντασμα''.<br>
'''φαντάζω (verbe)''' : Dénoncer ; Se montrer, apparaître.<br>
'''φαντασία, -ας (nom commun) (f)''' : imagination.<br>
'''φάντασμα, -άσματος (nom commun) (n)''' : Apparition, vision, songe ; fantôme, spectre.<br>
'''φανταστής, -οῦ (nom commun) (m)''' : vantard.<br>
'''φανταστικός, -ή, -όν (adjectif)''' : imaginaire.<br>
'''φάραγξ, -γος (nom commun) (m)''' : canyon.<br>
'''φαραώ (nom commun) (m)''' : pharaon.<br>
'''φαρέτρα, -ας (nom commun) (f)''' : carquois.<br>
'''φαρέτρη, -ης (nom commun) (f)''' : Forme dorienne de ''φαρέτρα''.<br>
'''φαρετρεών, -ῶνος (nom commun) (m)''' : .<br>
'''φαρισαϊκός, -ή, -όν (adjectif) (f)''' : pharisien.<br>
'''φαρισαϊκότατα, -, - (adverbe)''' : Superlatif de ''φαρισαϊκῶς''.<br>
'''φαρισαϊκότερον, -, - (adverbe)''' : Comparatif de ''φαρισαϊκῶς''.<br>
'''φαρισαϊκῶς (adverbe)''' : pharisiennement.<br>
'''φαρισαϊκώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''φαρισαϊκός''.<br>
'''φαρισαϊκώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''φαρισαϊκός''.<br>
'''φαρισαῖος, -ίου (nom commun) (m)''' : pharisien.<br>
'''φαρμακεία, -ας (nom commun) (f)''' : ensemble des médicaments.<br>
'''φαρμακεύς, -έως (nom commun) (m)''' : Apothicaire, empoisonneur. Sorcier.<br>
'''φαρμακευτικός, -ή, -όν (adjectif) (f)''' : de remède.<br>
'''φαρμακευτικότατα, -, - (adverbe)''' : Superlatif de ''φαρμακευτικῶς''.<br>
'''φαρμακευτικότερον, -, - (adverbe)''' : Comparatif de ''φαρμακευτικῶς''.<br>
'''φαρμακευτικῶς (adverbe)''' : .<br>
'''φαρμακευτικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''φαρμακευτικός''.<br>
'''φαρμακευτικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''φαρμακευτικός''.<br>
'''φαρμακίς, -δος (nom commun) (f)''' : Apothicaire, empoisonneuse. Sorcière.<br>
'''φάρμακον, -άκου (nom commun) (n)''' : Médicament, remède ; poison, préparation magique. Teinture, fard.<br>
'''φαρμακοποσία, -ας''' : action de boire une potion.<br>
'''φαρμακοπώλης, -ου (nom commun) (m)''' : pharmacien.<br>
'''φαρμακός, -οῦ (nom commun) (m)''' : victime expiatoire.<br>
'''φαρμακοτρίϐης, -ου (nom commun) (m)''' : laborantin.<br>
'''φαρμακώδης, -ης, -ες (adjectif)''' : médicinal ; vénéneux.<br>
'''φαρμακῶ (verbe)''' : empoisonner.<br>
'''φάρυγξ, -γος (nom commun) (m)''' : gosier.<br>
'''φάσηλος, -ήλου (nom commun) (m)''' : haricot sec.<br>
'''φασιανικός, -ή, -όν (adjectif)''' : phasianien.<br>
'''φασιανός, -οῦ (nom commun) (m)''' : faisan.<br>
'''φάκελος, -έλου (nom commun) (m)''' : fagot, faisceau.<br>
'''φάσκος, -ου (nom commun) (m)''' : .<br>
'''φασκώλιον, -ίου (nom commun) (n)''' : valisette.<br>
'''φάσκωλος, -ώλου (nom commun) (m)''' : valise.<br>
'''φάσμα, -τος (nom commun) (n)''' : spectre.<br>
'''φασματικός, -ή, -όν (adjectif)''' : spectral.<br>
'''φασματικώτατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''φασματικός''.<br>
'''φασματικώτερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''φασματικός''.<br>
'''φασματικῶς (adverbe)''' : spectralement.<br>
'''φασματικώτατα, -, - (adverbe)''' : Superlatif de ''φασματικῶς''.<br>
'''φασματικώτερον, -, - (adverbe)''' : Comparatif de ''φασματικῶς''.<br>
'''φάτις, -εως (nom commun) (f)''' : rumeur, parole.<br>
'''φάττα, -ης (nom commun) (f)''' : palombe ; ramier.<br>
'''φαῦλος, -ύλη, -ῦλον (adjectif)''' : vicieux.<br>
'''φέγγος, -ους (nom commun) (n)''' : .<br>
'''φέρετρον, -έτρου (nom commun) (n)''' : cercueil.<br>
'''φέρω (verbe)''' : porter.<br>
'''φεύγω (verbe)''' : fuir (prendre la fuite).<br>
'''φεῦξις, -ύξεως (nom commun) (f)''' : échappée, évasion ; fuite.<br>
'''φεῦ (interjection)''' : Hélas ; ah.<br>
'''φή (conjonction)''' : Forme homérique de ''ὡς''.<br>
'''φηγός, -οῦ (nom commun) (f)''' : chêne.<br>
'''φηλητής, -οῦ (nom commun) (m)''' : trompeur.<br>
'''φῆλος, -ος, -ον (adjectif)''' : trompeur.<br>
'''φήμη, -ης (nom commun) (f)''' : Voix prophétique, oracle. Rumeur, réputation.<br>
'''φημί (verbe)''' : dire.<br>
'''φήρ, -ός (nom commun) (m)''' : Forme éolienne de ''θήρ''.<br>
'''φθέγγομαι (verbe)''' : proférer.<br>
'''φθειρρός, -οῦ (nom commun) (m)''' : pou.<br>
'''φθείρω (verbe)''' : user.<br>
'''φθινόπωρον, -ώρου (nom commun) (n)''' : automne.<br>
'''φθίσις, -εως (nom commun) (f)''' : Déclin, ruine. Atrophie, consomption. Contraction, réduction de la pupille.<br>
'''φθίω (verbe)''' : périr, disparaitre.<br>
'''φθόγγος, -ου (nom commun) (m)''' : son.<br>
'''φθονερός, -ή, -όν (adjectif) (f)''' : envieux.<br>
'''φθόνησις, -ήσεως (nom commun) (f)''' : dénégation.<br>
'''φθόνος, -ου (nom commun) (m)''' : envie.<br>
'''φθονῶ (verbe)''' : envier.<br>
'''φθορά, -ᾶς (nom commun) (f)''' : Perdition, perte, ruine, destruction. Action de corrompre, corruption, séduction. (Peinture) Dégradation de couleur, affaiblissement de teinte.<br>
'''φῖ (nom commun) (n)''' : phi.<br>
'''φίλαμα, -άματος (nom commun) (n)''' : Forme dorienne de ''φίλημα''.<br>
'''φιλαδελφία, -ας (nom commun) (f)''' philadelphie.<br>
'''φιλανδρία, -ας (nom commun) (f)''' philandrie.<br>
'''φιλανθρωπία, -ας (nom commun) (f)''' philanthropie.<br>
'''φιλάνθρωπος, -ος, -ον (adjectif)''' : Humain, bon ; bienveillant, affable. Qui aime les hommes (en parlant des dieux). Qui plaît aux hommes, agréable.<br>
'''φιλαργυρία, -ας (nom commun) (f)''' : avarice.<br>
'''φιλάργυρος, -ος, -ον (adjectif)''' : avare.<br>
'''φίλειμι (verbe)''' : Forme béotienne de ''φιλῶ''.<br>
'''φίλημμι (verbe)''' : Forme éolienne de ''φιλῶ''.<br>
'''φιλία, -ας (nom commun) (f)''' : amitié, amour absolu, plaisir de la compagnie.<br>
'''φιλίη, -ης (nom commun) (f)''' : Forme ionienne de ''φιλία''.<br>
'''φίλημα, -ήματος (nom commun) (n)''' : baiser.<br>
'''φίλημμι (verbe)''' : Forme éolienne de ''φιλῶ''.<br>
'''φιλόγελως, -ωτος (nom commun) (m)''' : Amateur d'histoires drôles.<br>
'''φιλόπαις, -δός (nom commun) (m)''' : philopaide.<br>
'''φίλος, -η, -ον (adjectif)''' : amical.<br>
'''φίλος, -ου (nom commun) (m)''' : ami.<br>
'''φιλο- (préfixe)''' : qui aime.<br>
'''φιλογυνία, -ας (nom commun) (f)''' : philogynie.<br>
'''φιλοπαιδία, -ας (nom commun) (f)''' : philopaidie.<br>
'''φιλόπαις, -αίδος (nom commun) (m)''' : philopaide.<br>
'''φιλοσοφία, -ας (nom commun) (f)''' : philosophie.<br>
'''φιλοσοφικός, -ή, -όν (adjectif)''' : philosophique.<br>
'''φιλοσοφικότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''φιλοσοφικός''.<br>
'''φιλοσοφικότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''φιλοσοφικός''.<br>
'''φιλοσοφικῶς (adverbe)''' : philosophiquement.<br>
'''φιλοσοφικώτατα, -, - (adverbe)''' : Superlatif de ''φιλοσοφικῶς''.<br>
'''φιλοσοφικώτερον, -, - (adverbe)''' : Comparatif de ''φιλοσοφικῶς''.<br>
'''φιλόσοφος, -όφου (nom commun) (m)''' : philosophe.<br>
'''φιλοσοφῶ (verbe)''' : philosopher.<br>
'''φιλότης, -τος (nom commun) (f)''' : Amitié ; relation sexuelle.<br>
'''φιλύρα, -ας (nom commun) (f)''' : tilleul.<br>
'''φιλῶ (verbe)''' : Aimer d'amitié. Éprouver de l'amitié. Traiter en ami, regarder comme un ami. Donner un signe d'amitié. Aimer d'amour. (Par extension) Aimer. Voir volontiers, accueillir avec plaisir, approuver, agréer. Rechercher, poursuivre. Se plaire à.<br>
'''φίλως (adverbe)''' : amicalement.<br>
'''φιμός, -οῦ (nom commun) (m)''' : .<br>
'''φίμωσις, -ώσεως (nom commun) (f)''' : musellement.<br>
'''φίμωτρον, -ου (nom commun) (m)''' : muselière.<br>
'''φιμῶ (verbe)''' : museler.<br>
'''φιτύω (verbe)''' : engendrer.<br>
'''φλέγμα, -τος (nom commun) (n)''' : flegme.<br>
'''φλέγω (verbe)''' : brûler.<br>
'''φλέψ, -ϐός (nom commun) (f)''' : veine (vaisseau sanguin).<br>
'''φλέω (verbe)''' : sourdre.<br>
'''φλίϐω (verbe)''' : étendre, presser.<br>
'''φλόγωσις, -ώσεως (nom commun) (m)''' : inflammation.<br>
'''φλογῶ (verbe)''' : flamber.<br>
'''φλοιός, -οῦ (nom commun) (m)''' : écorce.<br>
'''φλόξ, -γός (nom commun) (m)''' : flamme.<br>
'''φλύαρος, -ος, -ον (adjectif)''' : bavard.<br>
'''φλυαρῶ (verbe)''' : bavarder.<br>
'''φλύκταινα, -ίνας (nom commun) (f)''' : cloque.<br>
'''φλύζω (verbe)''' : Forme de ''φλύω''.<br>
'''φλύω (verbe)''' : couler.<br>
'''φοϐερός, -ά, -όν (adjectif)''' : effrayant.<br>
'''φόϐος, -ου (nom commun) (m)''' : peur.<br>
'''φοῖϐος, -η, -ον (adjectif)''' : .<br>
'''φοῖνιξ, -ίνικος (nom commun) (m)''' : palmier-dattier ; phénix.<br>
'''φονεύς, -έως (nom commun) (m)''' : meurtrier.<br>
'''φονεύω (verbe)''' : assassiner.<br>
'''φονικός, -ή, -όν (adjectif)''' : meurtrier.<br>
'''φόνος, -ου (nom commun) (m)''' : meurtre.<br>
'''φόρημα, -ήματος (nom commun) (n)''' : .<br>
'''φόρμιγξ, -γος (nom commun) (f)''' : (poésie) lyre.<br>
'''φορτίζω (verbe)''' : charger.<br>
'''φόρτισις, -ίσεως (nom commun) (f)''' : charge.<br>
'''φόρτος, -ου (nom commun) (m)''' : .<br>
'''φοῦκτα, -ύκτας (nom commun) (m)''' : poignée de main.<br>
'''φοῦρνος, -ύρνου (nom commun) (m)''' : four.<br>
'''φραγή, -ῆς (nom commun) (f)''' : bloc.<br>
'''φράγμα, -ατος (nom commun) (n)''' : clôture.<br>
'''φραγμός, -οῦ (nom commun) (m)''' : bloc.<br>
'''φράν, -ός (nom commun) (f)''' : Forme dorienne de ''φρήν''.<br>
'''φράσις, -εως (nom commun) (f)''' : Suite de mots.<br>
'''φράσσω (verbe)''' : déclarer.<br>
'''φράτηρ, -ερος (nom commun) (m)''' : Membre d'une phratrie.<br>
'''φρατήρ, -έρος (nom commun) (m)''' : Forme dorienne de ''φράτηρ''.<br>
'''φρατρία, -ας (nom commun) (f)''' : phratrie.<br>
'''φρέαρ, -τος (nom commun) (n)''' : puits.<br>
'''φρενητικός, -ή, -όν (adjectif)''' : délirant.<br>
'''φρενητικῶς (adverbe)''' : -ment.<br>
'''φρενητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''φρενητικός''.<br>
'''φρενητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''φρενητικός''.<br>
'''φρενητικώτατα, -, - (adverbe)''' : Superlatif de ''φρενητικῶς''.<br>
'''φρενητικώτερον, -, - (adverbe)''' : Comparatif de ''φρενητικῶς''.<br>
'''φρενῖτις, -ίτιδος (nom commun) (f)''' : délire.<br>
'''φρήν, -ενός (nom commun) (f)''' : Toute membrane qui enveloppe un organe. (Au pluriel) Viscères, entrailles. (Par suite) (Poésie) Cœur, âme.<br>
'''φρήτηρ, -ερος (nom commun) (m)''' : Forme ionienne de ''φράτηρ''.<br>
'''φρίκη, -ης (nom commun) (f)''' : terreur.<br>
'''φρίξ, -ικός (nom commun) (f)''' : terreur.<br>
'''φριξός, -ή, -όν (adjectif)''' : terrifiant.<br>
'''φρίσσω (verbe)''' : terroriser.<br>
'''φρόνημα, -ήματος (nom commun) (n)''' : Intelligence, pensée. Manière de penser.<br>
'''φρόνησις, -ήσεως (nom commun) (f)''' : Pensée, dessein. Intelligence raisonnable, sagesse. Intelligence (ou sagesse) divine.<br>
'''φρόνιμος, -ος, -ον (adjectif)''' : sensé.<br>
'''φρονῶ (verbe)''' : Penser, avoir la faculté de penser ou de sentir, vivre. Être dans son bon sens. Penser. Être sensé. Avoir dans l’esprit. Songer à, projeter de.<br>
'''φρουρά, -ᾶς (nom commun) (m)''' : garde (corps d’armée).<br>
'''φρουρή, -ῆς (nom commun) (m)''' : Forme ionienne de ''φρουρή''.<br>
'''φρούρημα, -ήματος (nom commun) (n)''' : bannissement.<br>
'''φρούρησις, -ήσεως (nom commun) (f)''' : fuite, bannissement.<br>
'''φρουρητός, -ός, -όν (adjectif)''' : banni.<br>
'''φρούριον, -ίου (nom commun) (n)''' : forteresse.<br>
'''φρουρός, -οῦ (nom commun) (m)''' : garde (surveillant).<br>
'''φρουρῶ (verbe)''' : bannir.<br>
'''φρύγω (verbe)''' : rôtir.<br>
'''φρῦνος, -ύνου (nom commun) (m)''' : crapaud.<br>
'''φυγάς, -δος (nom commun) (m)''' : fugitif.<br>
'''φυγή, -ῆς (nom commun) (f)''' : fuite, bannissement.<br>
'''φύζω (verbe)''' : Forme ionienne de ''φεύγω''.<br>
'''φυίω (verbe)''' : Forme éolienne de ''φύω''.<br>
'''φυλακή, -ῆς (nom commun) (f)''' : garde, surveillance ; vigilance.<br>
'''φυλακτήριον, -ίου (nom commun) (n)''' : amulette.<br>
'''φυλακτήρ, -ῆρος (nom commun) (m)''' : garde.<br>
'''φυλάξις, -εως (nom commun) (f)''' : garde.<br>
'''φύλαξ, -κος (nom commun) (m)''' : observateur, garde ; protecteur.<br>
'''φυλάσσω (verbe)''' : garder.<br>
'''φυλάττω (verbe)''' : Forme attique de ''φυλάσσω''.<br>
'''φυλετικός, -ή, -όν (adjectif)''' : racial.<br>
'''φυλή, -ῆς (nom commun) (f)''' : Tribu, groupe de familles de même races ; (Militaire) Corps de troupes au nombre de 10. (Par extension) Classe, genre ; espèce.<br>
'''φύλλον, -ου (nom commun) (n)''' : feuille.<br>
'''φυλλόω (verbe)''' : .<br>
'''φῦλον, -ύλου (nom commun) (n)''' : groupe, tribu ; nation.<br>
'''φῦμα, -ύματος (nom commun) (n)''' : .<br>
'''φυμάτιον, -ίου (nom commun) (n)''' : .<br>
'''φύραμα, -τος (nom commun) (n)''' : .<br>
'''φυράω (verbe)''' : .<br>
'''φύρδην (adverbe)''' : .<br>
'''φυρατέον, -ου (nom commun) (n)''' : .<br>
'''φυρατής, -οῦ (nom commun) (m)''' : .<br>
'''φύρμα, -τος (nom commun) (n)''' : .<br>
'''φυρμός, -οῦ (nom commun) (m)''' : .<br>
'''φύρσιμος, -ίµου (nom commun) (m)''' : .<br>
'''φύρσις, -εως (nom commun) (f)''' : .<br>
'''φύρω (verbe)''' : .<br>
'''φυσσαλίς, -δος (nom commun) (m)''' : bulle.<br>
'''φῦσα, -ύσας (nom commun) (f)''' : .<br>
'''φυσάω (verbe)''' : .<br>
'''φυσητήρ, -ῆρος (nom commun) (m)''' : soupirail.<br>
'''φῦσιγξ, -ύσιγγος (nom commun) (m)''' : cartouche.<br>
'''φύσις, -εως (nom commun) (f)''' : nature.<br>
'''φυσικός, -ή, -όν, (adjectif)''' : naturel.<br>
'''φυσικῶς (adverbe)''' : naturellement.<br>
'''φυσικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''φυσικός''.<br>
'''φυσικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''φυσικός''.<br>
'''φυσικώτατα, -, - (adverbe)''' : Superlatif de ''φυσικῶς''.<br>
'''φυσικώτερον, -, - (adverbe)''' : Comparatif de ''φυσικῶς''.<br>
'''φύσκα, -ας (nom commun) (f)''' : Forme dorienne de ''φύσκη''.<br>
'''φύσκη, -ης (nom commun) (f)''' : .<br>
'''φύτευμα, -ύματος (nom commun) (n)''' : raiponce.<br>
'''φυτεύω (verbe)''' : planter.<br>
'''φυτόν, -οῦ (nom commun) (n)''' : végétal, plante.<br>
'''φυτο- (préfixe)''' : relatif aux plantes.<br>
'''φῦ (interjection)''' : pouah.<br>
'''φύω (verbe)''' : croître.<br>
'''φώκη, -ης (nom commun) (f)''' : phoque.<br>
'''φώκιος, -ος, -ον (adjectif)''' : phocidien.<br>
'''φωλεός, -οῦ (nom commun) (m)''' : tanière, terrier.<br>
'''φωνήεις, -σσα, -ῆεν (adjectif)''' : vocalique.<br>
'''φωνῆεν, -ήεντος (nom commun) (n)''' : voyelle.<br>
'''φωνή, -ῆς (nom commun) (f)''' : voix.<br>
'''φώνημα, -ήματος (nom commun) (n)''' : phonème.<br>
'''φωνητικός, -ή, -όν (adjectif)''' : phonétique.<br>
'''φωνητικῶς (adverbe)''' : phonétiquement.<br>
'''φωνητικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''φωνητικός''.<br>
'''φωνητικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''φωνητικός''.<br>
'''φωνῶ (verbe)''' : Faire entendre un son de voix. Parler haut, dire d'une voix forte, élever la voix Ordonner, commander, prescrire. Parler de. Chanter.<br>
'''φώς, -τός (nom commun) (m)''' : homme.<br>
'''φῶς, -τός (nom commun) (n)''' : lumière, éclair.<br>
'''φωσφορίζων, -ουσα, -ον (adjectif)''' : fluorescent.<br>
'''φωσφορίζω (verbe)''' : fluorescer.<br>
'''φωσφόρος, -ος, -ον (adjectif)''' : qui apporte la lumière.<br>
'''Φαέθουσα, -ας (nom propre) (f)''' : Phaéthuse.<br>
'''Φαέθων, -οντος (nom propre) (m)''' : Phaéton.<br>
'''Φαίδρα, -ας (nom propre) (f)''' : Phèdre.<br>
'''Φαῖδρος, -ίδρου (nom propre) (f)''' : Phèdre.<br>
'''Φαιδρία, -ας (nom propre) (f)''' : Phédrie.<br>
'''Φαμενώθ (nom propre) (m)''' : Phaminoth.<br>
'''Φαντασός, -οῦ (nom propre) (m)''' : Phantasos.<br>
'''Φάρος, -ου (nom propre) (f)''' : Pharos.<br>
'''Φαρμουθί (nom propre) (m)''' : Pharmouti.<br>
'''Φασιανός, -οῦ (nom commun) (m)''' : Phasien.<br>
'''Φᾶσις, -άσιος (nom propre) (m)''' : Phase.<br>
'''Φαῶφι (nom propre) (m)''' : Phaophi.<br>
'''Φειδίας, -ου (nom propre) (m)''' : Phidias.<br>
'''Φερεκύδης, -ου (nom propre) (m)''' : Phérécyde.<br>
'''Φερενίκη, -ης (nom propre) (f)''' : Véronique.<br>
'''Φηγεύς, -έως (nom propre) (m)''' : Phégée.<br>
'''Φθόνος, -ου (nom propre) (m)''' : [[wikt:Phtonos|Phtonos]].<br>
'''Φιλάμμων, -ονος (nom propre) (m)''' : Philammon. (Demi-frère d'Autolycos.)<br>
'''Φιλέας, -ου (nom propre) (m)''' : Philéas.<br>
'''Φίλιππος, -ίππου (nom propre) (m)''' : Philippe.<br>
'''Φιλόγελως, -τος (nom propre) (m)''' : Philogélos. (Recueil d’histoires drôles probablement rédigé au V{{e}} siècle apr. J.-C., attribué à Hiéroclès et Philagrios)<br>
'''Φιλοκτήτης, -ου (nom propre) (m)''' : Philoctète.<br>
'''Φιλομήλη, -ης (nom propre) (f)''' : Philomèle.<br>
'''Φιλότης, -τος (nom propre) (f)''' : [[wikt:Philotès|Philotès]].<br>
'''Φίλων, -ος (nom propre) (m)''' : Philo.<br>
'''Φιλώτας, -ου (nom propre) (m)''' : Philinte.<br>
'''Φινεές, -οῦ (nom propre) (m)''' : Phinées.<br>
'''Φινεύς, -έως (nom propre) (m)''' : Phinée.<br>
'''Φίξ, -γγός (nom propre) (f)''' : Forme béotienne de ''Σφίγξ''.<br>
'''Φλεγέθων, -οντος (nom propre) (m)''' : Phlégéthon.<br>
'''Φλεγύας, -ντος (nom propre) (m)''' : Phlégias. (père de Coronis)<br>
'''Φλέγων, -ος (nom propre) (m)''' : Phlégon.<br>
'''Φοϐητώρ, -όρος (nom propre) (m)''' : Phobétor.<br>
'''Φοίϐη, -ης (nom propre) (f)''' : [[wikt:Phœbé|Phœbé]].<br>
'''Φοιϐίδας, -ου (nom propre) (m)''' : [[wikt:Phébidas|Phébidas]].<br>
'''Φοῖϐος, -ίϐου (nom propre) (m)''' : [[wikt:Phébus|Phébus]].<br>
'''Φοῖνιξ, -ίνικος (nom commun) (m/f)''' : Phénicien. Carthaginois. (les descendants de Phénicie.)<br>
'''Φόϐος, -ου (nom propre) (m)''' : Phobos.<br>
'''Φραόρτης, -ου (nom propre) (m)''' : .<br>
'''Φρυγία, -ας (nom propre) (f)''' : Phrygie.<br>
'''Φρύξ, -γός (nom commun) (m)''' : Phrygien.<br>
'''Φυλεύς, -έως (nom propre) (m)''' : Phylée (fils d’Augias).<br>
'''Φυλλίς, -δος (nom propre) (f)''' : Phyllis.<br>
'''Φυσίγναθος, -άθου (nom propre) (f)''' : Physignathe.<br>
'''Φωκεύς, -έως (nom commun) (m)''' : Phocidien.<br>
'''Φώκαια, -ίας (nom propre) (f)''' : Phocée. (Ancienne cité grecque d’Asie Mineure.)<br>
'''Φωκαιεύς, -έως (nom commun) (m)''' : Phocéen.<br>
'''Φωκαιίς, -δος (nom commun) (f)''' : Phocéenne.<br>
'''Φωκίς, -δος (nom propre) (f)''' : Phocide.<br>
'''Φῶκος, -ώκου (nom propre) (m)''' : Phocus.<br>
'''Φορμίων, -ος (nom propre) (m)''' : Phormion.<br>
'''Φωτεινή, -ῆς (nom propre) (f)''' : Photine.<br>
==Χ==
'''χαίνω (verbe)''' : .<br>
'''χαῖρε (interjection)''' (Devient ''χαίρετε'' au pluriel.) : bonjour ; salut.<br>
'''χαίρω (verbe)''' : se réjouir ; être joyeux. Se réjouir d'ordinaire, se plaire d'ordinaire à ou dans. Avoir sujet de se réjouir.<br>
'''χάλαζα, -άζης (nom commun) (f)''' : grêle.<br>
'''χαλαρός, -ά, -όν (adjectif)''' : détendu.<br>
'''χαλαρότης, -τος (nom commun) (f)''' : détente.<br>
'''χαλάρωσις, -ώσεως (nom commun) (f)''' : détente.<br>
'''χαλαρῶς (adverbe)''' : .<br>
'''χαλαρῶ (verbe)''' : détendre.<br>
'''χαλάω (verbe)''' : Lâcher, relâcher. Laisser tomber, baisser, rabaisser. Laisser aller. (Au passif) Être adouci. Être indulgent. Céder le passage. Affaiblir.<br>
'''χάλασις, -άσεως (nom commun) (f)''' : Relâchement, relaxation.<br>
'''χαλεπός, -ή, -όν (adjectif)''' : difficile.<br>
'''χαλεπῶς (adverbe)''' : difficilement.<br>
'''χαλεπώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''χαλεπός''.<br>
'''χαλεπώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''χαλεπός''.<br>
'''χάλιξ, -κος (nom commun) (m/f)''' : galet ; gravier.<br>
'''χαλκεύς, -έως (nom commun) (m)''' : forgeron.<br>
'''χαλκός, -οῦ (nom commun) (m)''' : cuivre ; bronze, fer.<br>
'''χαλυϐήϊος, -ΐα, -ήϊον (adjectif)''' : d’acier.<br>
'''χάλυψ, -ϐος (nom commun) (m)''' : acier.<br>
'''χαμᾶζε (adverbe)''' : à terre (avec mouvement).<br>
'''χαμαί (adverbe)''' : à terre (sans mouvement).<br>
'''χαμαικέρασος, -άσου (nom commun) (f)''' : fraise.<br>
'''χαμαιλέων, -οντος (nom commun) (m)''' : caméléon.<br>
'''χαμαίμηλον, -ήλου (nom commun) (f)''' : camomille.<br>
'''χαμαιτυπεῖον, -ίου (nom commun) (n)''' : lupanar.<br>
'''χαμαιτύπη, -ης (nom commun) (f)''' : prostituée.<br>
'''χάν, -ός (nom commun) (m/f)''' : Forme dorienne de ''χήν''.<br>
'''χάος, -ους (nom commun) (n)''' : chaos.<br>
'''χαρά, -ᾶς (nom commun) (f)''' : joie ; plaisir.<br>
'''χαρακτηρίζω (verbe)''' : inscrire, marquer.<br>
'''χαρακτήρ, -ῆρος (nom commun) (m)''' : Fer pour marquer le bétail, marque faite au fer rouge. Marque, signe, empreinte, stigmate. Cachet, caractère, genre de style, style.<br>
'''χαράσσω (verbe)''' : Aiguiser. Gratter, couper.<br>
'''χάραξ, -κος (nom commun) (m)''' : pieu.<br>
'''χαριέντως (adverbe)''' : gracieusement.<br>
'''χαρίεις, -εσσα, -εν (adjectif)''' : gracieux.<br>
'''χαρίϝεις, -εσσα, -εν (adjectif)''' : Forme homérique de ''χαρίεις''.<br>
'''χαριέστατος, -άτη, -έστατον (adjectif)''' : Superlatif de ''χαρίεις''.<br>
'''χαριέστερος, -έρα, -ερον (adjectif)''' : Comparatif de ''χαρίεις''.<br>
'''χαρίϝεττα, -, - (adjectif)''' : Forme béotienne de ''χαρίεις''.<br>
'''χαρίζομαι (verbe)''' : .<br>
'''χάρις, -τος (nom commun) (f)''' : Ce qui brille ; ce qui réjouit. Grâce.<br>
'''χάρισμα, -ίσματος (nom commun) (n)''' : grâce accordée par Dieu.<br>
'''χαριτῶ (verbe)''' : .<br>
'''χάρμα, -τος (nom commun) (n)''' : source de joie, délice.<br>
'''χάσμα, -τος (nom commun) (n)''' : béance, ouverture ; creux, gouffre. Abysse.<br>
'''χάσκω (verbe)''' : bayer, béer.<br>
'''χαῦνος, -ύνη, -ῦνον (adjectif)''' : .<br>
'''χαυνότης, -τος (suffixe) (f)''' : .<br>
'''χαυνῶ (verbe)''' : .<br>
'''χέζω (verbe)''' : chier.<br>
'''χεῖμα, -ίματος (nom commun) (n)''' : hiver.<br>
'''χείμαρρος, -άρρου (nom commun) (m)''' : torrent.<br>
'''χειμών, -ῶνος (nom commun) (m)''' : (sens propre) Mauvais temps. (sens figuré) Trouble.<br>
'''χειραφέτησις, -ήσεως (nom commun) (f)''' : émancipation.<br>
'''χείριστος, -ίστη, -ίριστον (adjectif)''' : Superlatif de ''κακός''.<br>
'''χείρ, -ός (nom commun) (f)''' : main.<br>
'''χειρόγραφον, -άφου (nom commun) (n)''' : manuscrit.<br>
'''χειρόγραφος, -η, -ον (adjectif)''' : manuscrit.<br>
'''χείρων, -ων, -ῖρον (adjectif)''' : Comparatif de ''κακός''.<br>
'''χελιδών, -ονός (nom commun) (f)''' : hirondelle.<br>
'''χελώνη, -ης (nom commun) (f)''' : tortue.<br>
'''χερνίϐιον, -ίου (nom commun) (n)''' : pot de chambre.<br>
'''χερσόνησος, -ήσου (nom commun) (f)''' : péninsule.<br>
'''χέρσος, -ος, -ον (adjectif)''' : sec ; stérile.<br>
'''χέω (verbe)''' : jouir. (Profiter d'une chose que l'on possède.)<br>
'''χηλή, -ῆς (nom commun) (f)''' : pince ; serre de certains animaux.<br>
'''χηλός, -οῦ (nom commun) (m)''' : coffre.<br>
'''χήν, -ός (nom commun) (m/f)''' : jars ; oie.<br>
'''χήρα, -ας (nom commun) (f)''' : veuve.<br>
'''χῆρος, -ήρου (nom commun) (m)''' : veuf.<br>
'''χθές (adverbe)''' : hier.<br>
'''χθόνιος, -α, -ον (adjectif)''' : Relatif aux divinités infernales.<br>
'''χθών, -ονός (nom commun) (f)''' : Sol, terre. Terre, pays, contrée. Ensemble du sol terrestre, terre entière. Terre comme séjour des vivants et des morts.<br>
'''χῖ (nom commun) (n)''' : chi.<br>
'''χίλιοι (adjectif numéral)''' : mille.<br>
'''χιόνεος, -έα, -εον (verbe)''' : enneigé.<br>
'''χιονίζω (verbe)''' : enneiger.<br>
'''χιονόχρως (adjectif)''' : blanc comme neige.<br>
'''χιτών, -ῶνος (nom commun) (m)''' : chiton.<br>
'''χιών, -όνος (nom commun) (f)''' : neige.<br>
'''χλαμύς, -δος (nom commun) (n)''' : chlamyde.<br>
'''χλευάζω (verbe)''' : se moquer.<br>
'''χλευασία, -ας (nom commun) (f)''' : moquerie.<br>
'''χλευασμός, -οῦ (nom commun) (m)''' : Moquerie ; blague.<br>
'''χλιαρός, -ή, -όν (adjectif)''' : tiède.<br>
'''χλωμός -ή -όν (adjectif)''' : pâle.<br>
'''χλωρός, -ά, -όν (adjectif)''' : Vert ; jaune verdâtre.<br>
'''χοῖρος, -ίρου (nom commun) (m)''' : Petit cochon. (Par extension) Cochon engraissé, porc.<br>
'''χόνδρος, -ου (nom commun) (m)''' : Petit corps dur et rond, grain. Grain de froment et d’épeautre. Froment, épeautre. Cartilage.<br>
'''χονδρός, -ά, -όν (adjectif)''' : gros.<br>
'''χονδρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''χονδρός''.<br>
'''χονδρότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''χονδρός''.<br>
'''χονδρύνω (verbe)''' : grossir.<br>
'''χονδρῶς (adverbe)''' : .<br>
'''χοραύλης, -ου (nom commun) (m)''' : choraule.<br>
'''χοραυλικός, -ή, -όν (adjectif)''' : choraulique.<br>
'''χορδή, -ῆς (nom commun) (f)''' : ficelle.<br>
'''χορεία, -ας (nom commun) (f)''' : danse.<br>
'''χορεῖος, -ίου (nom commun) (m)''' : chorée.<br>
'''χορευτής, -οῦ (nom commun) (m)''' : danseur.<br>
'''χορεύω (verbe)''' : danser.<br>
'''χορήγιον, -ίου (nom commun) (m)''' : chorégie.<br>
'''χορηγός, -οῦ (nom commun) (m)''' : chorège.<br>
'''χορικός, -ή, -όν (adjectif)''' : chorique.<br>
'''χοροκιθαριστής, -οῦ (nom commun) (m)''' : chorocithariste.<br>
'''χορός, -οῦ (nom commun) (m)''' : danse en ronde.<br>
'''χόρτος, -ου (nom commun) (m)''' : Enclos. Pré clôturé ; pâturage. Nourriture.<br>
'''χορῳδία, -ας (nom commun) (f)''' : .<br>
'''χράω (verbe)''' : Frapper, attaquer. Infliger, asséner. Désirer ardemment. Proclamer. (Au passif) Être déclaré par un oracle. (Voix moyenne) Consulter un oracle, être averti par un oracle. Fournir, prêter. Utiliser. Avoir besoin de.<br>
'''χρεία, -ας (nom commun) (f)''' : Usage, emploi. Manière dont on fait usage. Profit qu'on retire d'un usage. Besoin, nécessité.<br>
'''χρείη, -ης (nom commun) (f)''' : Forme ionienne de ''χρεία''.<br>
'''χρεώ (verbe)''' : avoir besoin.<br>
'''χρῄζω (verbe)''' : nécessiter, désirer ; vouloir.<br>
'''χρή (verbe)''' : il faut.<br>
'''χρῆμα, -ήματος (nom commun) (n)''' : Chose dont on se sert, ou dont on a besoin. (Au singulier) Ce dont on se sert, chose, affaire. Évènement, occurrence. (Au pluriel) Bien, avoir.<br>
'''χρῆσις, -ήσεως (nom commun) (f)''' : .<br>
'''χρησμός, -οῦ (nom commun) (f)''' : oracle.<br>
'''χρηστομάθεια, -ίας (nom commun) (f)''' : savoir utile.<br>
'''χρηστός, -ή -όν (adjectif)''' : Dont on peut se servir. Qui rend service.<br>
'''χρηστότης, -τος (nom commun) (f)''' : Bonne qualité, bonté. (En particulier) Bonté de cœur.<br>
'''χρίζω (verbe)''' : oindre.<br>
'''χρῖσμα, -ίσματος (nom commun) (n)''' : onction.<br>
'''χρονικός, -ή -όν (adjectif)''' : temporel.<br>
'''χρονικῶς (adverbe)''' : temporellement.<br>
'''χρονικώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''χρονικός''.<br>
'''χρονικώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''χρονικός''.<br>
'''χρόνιος, -ία -όνιον (adjectif)''' : tardif.<br>
'''χρονίως (adverbe)''' : tardivement.<br>
'''χρονιώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''χρονίως''.<br>
'''χρονιώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''χρονίως''.<br>
'''χρόνος, -ου (nom commun) (m)''' : temps (durée).<br>
'''χρυσαίετος, -ου (nom commun) (m)''' : aigle royal.<br>
'''χρυσάνθεμον, -έμου (nom commun) (n)''' : chrysanthème.<br>
'''χρυσόγονον, -όνου (nom commun) (n)''' : navet noir.<br>
'''χρυσομηλιά, -ᾶς (nom commun) (f)''' : orange.<br>
'''χρυσός, -οῦ (nom commun) (m)''' : or.<br>
'''χρῶμα, -ώματος (nom commun) (n)''' : couleur.<br>
'''χυδαῖος, -ίη ,-ῖον (adjectif)''' : s’affaissant ; commun. Vulgaire.<br>
'''χυδαιολογία, -ας (nom commun) (f)''' : .<br>
'''χυλός, οῦ (nom commun) (m)''' : gruau.<br>
'''χυλοπίτα, -ας (nom commun) (f)''' : .<br>
'''χυλώδης, -ης, -ες (adjectif)''' : .<br>
'''χύλωμα, -τος (nom commun) (n)''' : .<br>
'''χυλώνω (verbe)''' : .<br>
'''χύμα, -τος (nom commun) (n)''' : suc.<br>
'''χυμεία, -ας (nom commun) (f)''' : .<br>
'''χυµός, -οῦ (nom commun) (m)''' : jus.<br>
'''χωλαίνω (verbe)''' : boiter.<br>
'''χωλίαμϐος, -άμϐου (nom commun) (m)''' : choliambe.<br>
'''χωλός, -ή, -όν (adjectif)''' : boiteux. (En parlant de l'intelligence) Dont l'esprit cloche, infirme d'esprit. Mal équilibré, instable, chancelant.(Rhétorique) Boiteux. (Métrique) Qui n'est pas sur ses pieds, boiteux.<br>
'''χωλότης, -τος (nom commun) (f)''' : .<br>
'''χωλῶς (adverbe)''' : en boitant.<br>
'''χωλότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''χωλός''.<br>
'''χωλότερος, -έρα, -ότερον (adjectif)''' : Comparatif de ''χωλός''.<br>
'''χῶμα, -ώματος (nom commun) (n)''' : (Militaire) Terrassement. (Par extension) Toute jetée, même en pierres, môle.<br>
'''κώμη, -ης (nom commun) (f)''' : village.<br>
'''χώρα, -ας (nom commun) (f)''' : Lieu. Place, endroit. (Militaire) Poste, position. (Au sens abstrait) Moment. Pays, région. Campagne. Champ, ferme.<br>
'''χώρη, -ης (nom commun) (f)''' : Forme ionienne de ''χώρα''.<br>
'''χωρίζω (verbe)''' : séparer.<br>
'''χωρίον, -ου (nom commun) (n)''' : district.<br>
'''χωρισμός, -οῦ (nom commun) (m)''' : séparation.<br>
'''χωρίς (adverbe)''' : Séparément ; à part. (Avec le génitif) sans. (Par suite) différemment.<br>
'''Χαιρέας, -ου (nom propre) (m)''' : Chéréas.<br>
'''Χαιρεφῶν, -ῶντος (nom propre) (m)''' : Chariphon.<br>
'''Χαιρώνεια, -ίας (nom propre) (f)''' : Chéronée (cité de Béotie).<br>
'''Χαιρωνεύς, -έως (nom propre) (m)''' : Chéronéen.<br>
'''Χαλδαία, -ας (nom propre) (f)''' : Chaldée.<br>
'''Χαλδαῖα, -ίας (nom commun) (f)''' : Chaldéenne.<br>
'''Χαλδαϊκός, -ή, -όν (adjectif)''' : chaldéen.<br>
'''Χαλδαῖος, -ίου (nom commun) (m)''' : Chaldéen.<br>
'''Χαλκηδών, -όνος (nom commun) (f)''' : Chalcédoine.<br>
'''Χαλκίς, -δος (nom commun) (f)''' Chalcis.<br>
'''Χάλυψ, -ϐος (nom propre) (m)''' : Chalybe.<br>
'''Χαπύ (nom propre) (m)''' : Hâpy.<br>
'''Χάρις, -τος (nom propre) (f)''' : Charite.<br>
'''Χαρίτων, -ος (nom propre) (m)''' : Chariton.<br>
'''Χάρυϐδις, -ύϐδεως (nom propre) (f)''' : Charybde.<br>
'''Χάρων, -ος (nom propre) (m)''' : Charon. (nocher des Enfers)<br>
'''Χέϐρης, -ου (nom propre) (m)''' : Toutânkhamon.<br>
'''Χείρων, -ος (nom propre) (m)''' : Chiron.<br>
'''Χένερης, -ου (nom propre) (m)''' : Khâsekhemoui.<br>
'''Χέοψ, -πος (nom propre) (m)''' : Khéops.<br>
'''Χεφρήν, -ένος (nom propre) (m)''' : Khéphren.<br>
'''Χηµία, -ας (nom propre) (f)''' : Égypte.<br>
'''Χθών, -ονός (nom propre) (f)''' : Chthon.<br>
'''Χιόνη, -ης (nom propre) (f)''' : Chioné.<br>
'''Χλόη, -ης (nom propre) (f)''' : Chloé.<br>
'''Χλωρίς, -δος (nom propre) (f)''' : Chloris (nymphe).<br>
'''Χνοῦϐις, -ύϐιδος (nom propre) (m)''' : [[wikt:Khnoum|Khnoum]].<br>
'''Χοίακ (nom propre) (m)''' : Choeac.<br>
'''Χοσρόης, -ου (nom propre) (m)''' : Chosroès.<br>
'''Χριστόφορος, -όρου (prénom) (m)''' : Christophe.<br>
'''Χριστός, -οῦ (nom commun) (m)''' : Christ.<br>
'''Χρυσάωρ, -ου (nom propre) (m)''' : Chrysaor.<br>
'''Χρύσιππος, -ίππου (nom propre) (m)''' : Chrysippe.<br>
'''Χρυσόμαλλον Δέρας (locution nominale)''' : Toison d'or.<br>
'''Χρυσόμαλλος, -ου (nom propre) (m)''' : Chrysomallos.<br>
'''Χρυσόπολις, -όλεως (nom propre) (m)''' : Üsküdar.<br>
==Ψ==
'''ψάλλω (verbe)''' : chanter.<br>
'''ψαλμός, -οῦ (nom commun) (m)''' : psaume.<br>
'''ψαλτήριον, -ίου (nom commun) (n)''' : psaltérion.<br>
'''ψάλτιγξ, -γος (nom commun) (m)''' : psaltinx.<br>
'''ψάρ, -ός (nom commun) (m)''' : étourneau.<br>
'''ψαρός, -ά, -όν (adjectif)''' : .<br>
'''ψαῦμα, -ύματος (nom commun) (n)''' : palpation.<br>
'''ψαῦσις, -ύσεως (nom commun) (f)''' : palpation.<br>
'''ψαύω (verbe)''' : palper.<br>
'''ψᾶφαξ, -άφακου (nom commun) (m)''' : Forme éolienne de ''ψῆφος''.<br>
'''ψᾶφος, -άφου (nom commun) (m)''' : Forme dorienne de ''ψῆφος''.<br>
'''ψάω (verbe)''' : Frotter, gratter.<br>
'''ψέγω (verbe)''' : Blâmer, reprocher.<br>
'''ψευδής, -ής, -ές (adjectif)''' : faux.<br>
'''ψεύδομαι (verbe)''' : mentir.<br>
'''ψεῦδος, -ύδους (nom commun) (n)''' : tromperie.<br>
'''ψευδῶς (adverbe)''' : faussement.<br>
'''ψευδώτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ψευδής''.<br>
'''ψευδώτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ψευδής''.<br>
'''ψεύδω (verbe)''' : tromper.<br>
'''ψεῦσμα, -ύσματος (nom commun) (n)''' : mensonge.<br>
'''ψεύστης, -ου (nom commun) (m)''' : menteur.<br>
'''ψεύστρα, -ας (nom commun) (f)''' : menteuse.<br>
'''ψῆγμα, -ήγματος (nom commun) (n)''' : pépite.<br>
'''ψηλάφησις, -ήσεως (nom commun) (f)''' : palpation.<br>
'''ψηλαφῶ (verbe)''' : palper.<br>
'''ψήρ, -ός (nom commun) (m)''' : Forme ionienne de ''ψάρ''.<br>
'''ψῆφος, -ήφου (nom commun) (m)''' : Galet, caillou, pierre, en particulier utilisée pour le calcul. Gemme, pierre précieuse.<br>
'''ψήχω (verbe)''' : racler.<br>
'''ψῖ (nom commun) (n)''' : psi.<br>
'''ψιλός, -ή, -όν (adjectif)''' : Nu ; simple.<br>
'''ψίξ, -χός (nom commun) (m/f)''' : Mie ; miette.<br>
'''ψιττακός, -οῦ (nom commun) (m)''' : perroquet.<br>
'''ψιχίον, -ου (nom commun) (n)''' : miette.<br>
'''ψίω (verbe)''' : .<br>
'''ψόα, -ας (nom commun) (f)''' : lombes.<br>
'''ψόγος, -ου (nom commun) (m)''' : Blâme, reproche.<br>
'''ψόλος, -ου (nom commun) (m)''' : suie.<br>
'''ψό (interjection)''' : beurk.<br>
'''ψυδρεύς, -έως (nom commun) (m)''' : trompeur.<br>
'''ψυδρός, -ά, -όν (adjectif)''' : faux.<br>
'''ψυχεινός, -ή, -όν (adjectif)''' : frais.<br>
'''ψυχή, -ῆς (nom commun) (f)''' : Âme ; papillon.<br>
'''ψύθος, -ους (nom commun) (n)''' : Forme homérique de ''ψεῦδος''.<br>
'''ψυχοπομπός, -ός, -όν (adjectif)''' : psychopompe.<br>
'''ψυχοπομπός, -οῦ (nom commun) (m)''' : psychopompe.<br>
'''ψῦχος, -ύχους (nom commun) (n)''' : fraîcheur.<br>
'''ψυχραίνω (verbe)''' : refroidir.<br>
'''ψυχρός, -ά, -όν (adjectif)''' : Froid ; glacial.<br>
'''ψυχρότατος, -άτη, -ότατον (adjectif)''' : Superlatif de ''ψυχρός''.<br>
'''ψυχρότερος, -έρη, -ότερον (adjectif)''' : Comparatif de ''ψυχρός''.<br>
'''ψυχρῶς (adverbe)''' : Froidement ; glacialement.<br>
'''ψύχω (verbe)''' : Souffler, respirer. Se refroidir.<br>
'''ψχέντ (nom commun) (m)''' : pschent.<br>
'''ψωμός, -οῦ (nom commun) (m)''' : bouchée de pain.<br>
'''Ψαπφώ, -οῦς (nom propre) (f)''' : Forme éolienne de ''Σαπφώ''.<br>
'''Ψίλαξ, -κος (nom propre) (m)''' : Psilax.<br>
'''Ψιχάρπαξ, -γος (nom propre) (m)''' : Psicharpax.<br>
'''Ψουσέννης, -ου (nom propre) (m)''' : Psousennès.<br>
'''Ψυχή, -ῆς (nom propre) (f)''' : Psyché.<br>
'''Ψωφίς, -δος (nom propre) (f)''' : Psophis.<br>
==Ω==
'''ᾠδή, -ῆς (nom commun) (f)''' : chant.<br>
'''ὠδυσάμην, - ()''' : .<br>
'''ὠθῶ (verbe)''' : pousser, forcer ; (militaire) repousser l’ennemi.<br>
'''ὠκεανός, -οῦ (nom commun) (m)''' : océan.<br>
'''ὠκέως (adverbe)''' : rapidement.<br>
'''ὠκύς, -εῖα, -ύ (adjectif)''' : rapide.<br>
'''ὠκύτατος, -άτη, -ώτατον (adjectif)''' : Superlatif de ''ὠκύς''.<br>
'''ὠκύτερος, -έρα, -ώτερον (adjectif)''' : Comparatif de ''ὠκύς''.<br>
'''ὦλαξ, ὤλακος (nom commun) (m)''' : Forme dorienne de ''αὖλαξ''.<br>
'''ὠλένη, -ης (nom commun) (f)''' : coude, coudée ; avant-bras.<br>
'''ὠμοπλάτη, -ης (nom commun) (f)''' : omoplate.<br>
'''ὦμος, ὤμου (nom commun) (m)''' : épaule.<br>
'''ὠνέομαι (verbe)''' : acheter.<br>
'''ὠνητής, -οῦ (nom commun) (m)''' : client.<br>
'''ὦνος, ὤνου (nom commun) (m)''' : prix.<br>
'''ᾠόν, -οῦ (nom commun) (n)''' : œuf.<br>
'''ὦ μέγα (nom commun) (n)''' : oméga.<br>
'''ὤρα, -ας (nom commun) (f)''' : soin ; souci.<br>
'''ὥρα, -ας (nom commun) (f)''' : heure.<br>
'''ὡραῖος, -ία, -ῖον (adjectif)''' : Qui est de la saison. Qui se fait à une époque, dans une saison déterminée. Qui est dans la fleur de l’âge.<br>
'''ὠρανός, -οῦ (nom commun) (m)''' : Forme dorienne et béotienne de ''οὐρανός''.<br>
'''ὡρεῖον, -ίου (nom commun) (n)''' : grenier.<br>
'''ὤρη, -ης (nom commun) (f)''' : Forme ionienne de ''ὤρα''.<br>
'''ὥρη, -ης (nom commun) (f)''' : Forme ionienne de ''ὥρα''.<br>
'''ὥριμος, -ος, -ον (adjectif)''' : mûr.<br>
'''ὡρολόγιον, -ίου (nom commun) (n)''' : horloge.<br>
'''ὡσαννά (interjection)''' : hosanna.<br>
'''ὦσις, ὤσεως (nom commun) (f)''' : poussée.<br>
'''ὦς, -τός (nom commun) (n)''' : Forme dorienne de ''οὖς''.<br>
'''ὡς (adverbe ; conjonction)''' : .<br>
'''ὥτερος, -έρα, -ερον (adjectif)''' : Autre forme dorienne de ''ἕτερος''.<br>
'''ὠτικός, -ή, -όν (adjectif)''' : otique.<br>
'''ὠτίον, -ίου (nom commun) (n)''' : Diminutif de '' οὖς''.<br>
'''ὠτίς, -δος (nom commun) (f)''' : outarde.<br>
'''ὦτος, ὤτου (nom commun) (m)''' : hibou.<br>
'''ὠφέλεια, -ίας (nom commun) (f)''' : Aide ; secours.<br>
'''ὠφέλημα, -ήματος (nom commun) (n)''' : .<br>
'''ὠφέλησις, -ήσεως (nom commun) (f)''' : Aide ; secours.<br>
'''ὠφελῶ (verbe)''' : Aider ; secourir.<br>
'''ὠχρός, -ά, -όν (adjectif)''' : pâle.<br>
'''ὤψ, -πός (nom commun) (f)''' : Vue ; visage.<br>
'''ὦ (particule)''' : ô.<br>
'''ὤ (interjection)''' : ah !, oh !<br>
'''Ὠγυγία, -ας (nom propre) (f)''' : Ogygie.<br>
'''Ὠγυγίη, -ης (nom propre) (f)''' : Forme ionienne de ''Ὠγυγία''.<br>
'''Ὠκεανίς, -δος (nom propre) (f)''' : Océanide.<br>
'''Ὠκεανός, -οῦ (nom propre) (m)''' : Océan.<br>
'''Ὠρανός, -οῦ (nom propre) (m)''' : Forme dorienne et béotienne de ''Οὐρανός''.<br>
'''Ὠρείθυια, -ίας (nom commun) (f)''' : Orithye (fille d'Érechthée).<br>
'''Ὠριγένης, -ου (nom propre) (m)''' : Origène.<br>
'''Ὠρομέδων, -οντος (nom propre) (m)''' : Oromédon.<br>
'''Ὧρος, Ὥρου (nom propre) (m)''' : [[wikt:Horus|Horus]].<br>
ayzsauz281vvgc8nuug61a4ozcqaupk
Fonctionnement d'un ordinateur/La désambiguïsation mémoire
0
65845
773411
765329
2026-09-28T00:37:02Z
Mewtow
31375
/* La file de µops mémoire */
773411
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu la mal-nommée exécution dans le désordre dans les chapitres précédents, qui devrait plutôt s'appeler l'émission dans le désordre. Elle modifie l'ordre des instructions pour gagner en efficacité, mais cela n'est valable que pour les instructions travaillant sur des registres ! Nous n'avons pas parlé de l'exécution dans le désordre des accès mémoire, car ils sont un peu à part. Et pour comprendre pourquoi, nous allons dédier ce chapitre à la gestion des accès mémoire dans le pipeline.
==L’émission des accès mémoire==
Avant de poursuivre, faisons un rappel rapide. Il faut distinguer deux types de dépendances de données : les dépendances de registres (deux instructions manipulent le même registre), alors que l'unité mémoire gère les dépendances d'adresse (deux instructions manipulent la même adresse). La différence registre-adresse fait que la gestion des dépendances est totalement différente.
===L'''aliasing'' mémoire et les dépendances mémoire===
Pour rappel, deux instructions ont une dépendance d'adresse si elles écrivent ou lisent la même adresse mémoire, ce qui interdit de changer leur ordre. Des instructions mémoires qui lisent/écrivent des adresses différentes sont indépendantes. Lorsque deux instructions mémoire lisent/écrivent à la même adresse, on parle d''''aliasing mémoire'''. Détecter les cas d'aliasing mémoire est primordial pour gérer la désambiguïsation mémoire, et cela demande de comparer des adresses.
Formellement, l'aliasing mémoire n'est ni plus moins que l'application des dépendances de données aux instructions mémoire. Et comme les autres dépendances de données, il existe plusieurs types d'aliasing mémoire : RAR, RAW, WAR et WAW. Les dépendances RAR ne posent aucun problème, les lectures peuvent changer d'ordre sans aucun problème, tant qu'il n'y a pas d'écritures entre les deux. Par contre, les autres dépendances imposent trois contraintes.
* Les dépendances RAW imposent que les lectures ne doivent pas s'exécuter avant une écriture à la même adresse.
* Les dépendances WAR imposent que l'on ne peut pas déplacer une écriture avant une lecture à la même adresse.
* Enfin, les dépendances WAW font que l'ordre des écritures doit être celui du programme : impossible de changer l'ordre de deux écritures.
Les trois contraintes peuvent s'implémenter de plusieurs manières différentes. Comme pour les dépendances de données normales, seules les dépendances RAW sont de vraies dépendances de données. Les deux autres dépendances viennent du fait que l'on utilise la même adresse pour stocker des valeurs différentes. S'il existe des techniques similaires au renommage de registre permettent de les faire disparaitre, elles sont très limitées.
===La désambiguïsation mémoire===
Diverses optimisations permettent d'émettre des accès mémoire dans le désordre, exactement comme l’exécution dans le désordre le fait pour les autres instructions. Elles utilisent pour cela des fenêtres d'instruction spécialisées dans les accès mémoire. Il faut vraiment insister sur le fait que l'exécution dans le désordre proprement dite ne se préoccupe pas des micro-opérations mémoire. Pour faire la distinction, on parle de '''désambiguïsation mémoire''' (''memory disambiguation'') pour parler de l'exécution dans le désordre des accès mémoire.
Le but de la désambiguïsation mémoire est d'exécuter les lectures le plus tôt possible. La raison est que la donnée lue est utilisée par d'autres instructions, dépendantes de la lecture. Elles sont donc à la tête d'une chaine de dépendances de données qui peut bloquer le pipeline si elle n'est pas résolue très tôt. Dès qu'une lecture peut être lancée, elle accède directement au cache de données. Par contre, si l'aliasing ne le permet pas (écriture à la même adresse pas encore effectuée) ou que l'adresse à lire n'a pas encore été calculée, la lecture attend son tour.
Le problème est que la désambiguïsation mémoire demande de comparer des adresses, pour détecter les dépendances d'adresse. Et cela pose de sérieuses contraintes pour son implémentation. En comparaison, l'exécution dans le désordre normale compare des registres, ce qui est beaucoup plus simple. Les registres utilisés par une instruction sont directement encodés dans l'instruction elle-même, ce qui fait qu'on peut détecter les dépendances de registre à l'émission, voire au décodage. Et elles peuvent même être éliminées par le renommage de registre pour les dépendances WAW et WAR. Par contre, les adresses sont des opérandes d'instruction, elles ne sont pas encodées dans l'instruction elle-même. Il y a certes d'exception de l'adressage absolu, mais elle est minoritaire. Avec l'adressage indirect ou indicé, les adresses sont soit lues depuis les registres, soit calculées par une unité de calcul. Les adresses ne sont donc connues qu'une fois que l'instruction a attendu suffisamment de temps dans la fenêtre d'instruction.
===Le calcul anticipé des adresses mémoire===
La désambiguïsation mémoire fonctionne d'autant mieux que les adresses à lire/écrire sont connues le plus tôt possible. Pour cela, il est possible de séparer les accès à la mémoire en deux micro-instructions : une pour le calcul d'adresse et l'autre pour les accès mémoire. Cela permet de calculer les adresses dès que possibles, et donc de vérifier à l'avance si l'adresse calculée a des dépendances avec celles des autres instructions. La détection des dépendances est ainsi anticipée de quelques cycles, permettant un accès anticipé sûr des accès mémoire. On parle de '''calcul anticipé des dépendances'''.
Une implémentation possible effectue les calculs d'adresse dans les unités de calcul entière. L'ALU entière et l'unité mémoire sont alors dans deux avals séparés, en parallèle. La micro-opération de calcul d'adresse s'exécute dans une ALU, et son résultat est envoyé à l'unité mémoire. Pour cela, l'adresse calculée est envoyée à l'unité mémoire via un système de contournement dédié, qui relie les sorties des ALU entières à l'unité mémoire.
Une solution alternative utilise un aval unique qui s'occupe à la fois des micro-opérations entières et des micro-opérations mémoire. L'aval a alors deux étages en série, un pour l'unité de calcul, suivi par l'étage d'accès mémoire. Le pipeline RISC classique était dans ce cas, pour rappel. Cette solution permet d'avoir un pipeline dit de longueur fixe, à savoir que toutes les instructions font le même nombre de cycles d'horloge. De plus, elle garantit que les instructions mémoire sont décodées en une seule micro-opération. En clair, il y a un seul aval pour toutes les instructions. Mais elle vient avec un défaut majeur : de nombreuses instructions n'utilisent pas l'unité mémoire, mais doivent quand même traverser l'étage associé, qui passe un cycle à ne rien faire.
==L’émission dans l'ordre des accès mémoire==
Avant toute chose, faisons quelques rappels sur l'exécution dans le désordre. Les micro-opérations renommées sont mises en attente avant que les conditions pour leur émission soient remplies. La mise en attente peut se faire soit dans une file de micro-opération, soit dans une fenêtre d'instruction. Une file de micro-opération est une mémoire FIFO qui garde les micro-opération dans leur ordre d'émission, l'ordre du programme. Les micro-opérations sont donc émises dans l'ordre du programme, il n'y a pas d'émission dans le désordre. Par contre, les fenêtres d'instructions peuvent émettre les micro-opérations dans le désordre, dans un ordre différent de celui du programme. Dans les deux cas, les écritures sont retardées via une file d'écriture, qu'on a vu il y a quelques chapitres et sur laquelle on fera des rappels.
===La file de µops mémoire===
Avec l''''émission dans l'ordre des accès mémoire''', le processeur émet les accès mémoire dans l'ordre du programme. Cela implique que l'unité mémoire n'a pas de fenêtre d'instruction, ni de station de réservation associée. À la place, les micro-opérations mémoire sont placées dans une file de micro-opération dédiée, appelée la '''file de µops mémoire'''. Par contre, pour les unités de calcul, le processeur utilise des fenêtres d'instruction. Autant les accès mémoire se font dans l'ordre, autant les instructions arithmétiques/logiques/branchements/autres sont exécutées dans le désordre. En clair, le processeur peut supporter l'exécution dans le désordre, sauf pour les micro-opérations mémoire.
[[File:Processeur avec émission dans l'ordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans l'ordre des accès mémoire]]
: Nous avons volontairement omis le cas où le processeur n'a pas d'exécution dans le désordre, où il y a une file de micro-opération unique. Mais c'est globalement la même chose, car une file de micro-opération unifiée émet toutes les instructions dans l'ordre.
La file de µops mémoire est une mémoire FIFO, qui contient plusieurs entrées, chaque entrée pouvant accueillir une µops mémoire. Une entrée contient plusieurs champs : un qui précise si l'opération est une lecture ou une écriture, un pour l'adresse mémoire à lire/écrire, un autre pour la donnée à écrire (qui est vide pour une lecture), un autre pour le registre de destination des lectures. Les deux derniers champs sont souvent fusionnés en un seul, qui est interprété différemment selon que l'instruction est une lecture ou une écriture. Elle contient aussi des bits de validité, qui indique si l'adresse ou la donnée sont disponibles, ou si le champ associé est vide.
[[File:Load Store Queue unifiée.png|centre|vignette|upright=2.5|File de µops mémoire.]]
L'unité mémoire ne permet que de faire un accès mémoire à la fois. Du moins, elle a un port sur lequel on envoie la micro-opération émise, que ce soit une écriture ou une lecture. Vu de l'extérieur, il faut attendre qu'un accès mémoire soit terminé pour lancer le suivant. Avant d'émettre une micro-opération mémoire, la file de micro-opération (son ''scoreboard'') vérifie si l'unité d'accès mémoire est libre ou non. Si elle est occupée, un accès mémoire est en cours, et on ne peut pas émettre de micro-opération mémoire. Une fois l'accès mémoire terminé, la mémoire ou le cache envoient un signal pour indiquer que l'accès est fini, et c'est au tour d'une nouvelle micro-opération mémoire.
[[File:Unité de gestion des accès mémoires dans l’ordre.png|centre|vignette|upright=2.0|Unité de gestion des accès mémoires dans l’ordre]]
Mais il s'agit là du comportement vu de l'extérieur de l'unité mémoire. En réalité, l'unité mémoire utilise quelques optimisations en interne qui permettent d'exécuter dans le désordre les micro-opérations mémoire.
===La file d'écriture : une FIFO de mise en attente des écritures===
Afin de supporter les exceptions précises, les écritures doivent respecter deux contraintes : s'exécuter dans le tout dernier étage du pipeline, s'exécuter dans l'ordre du programme. Pour cela, les écritures sont mises en attente tant que les instructions précédentes ne sont pas terminées. Les écritures dans le cache sont mises en attente dans une mémoire FIFO, appelée la '''file d'écriture''', ou encore la ''Write Queue''. Nous en avions déjà parlé dans le chapitre sur le pipeline, dans la section sur les exceptions précises. Mais quelques rappels ne feront pas de mal.
L'écriture est insérée dans la file d'écriture lorsqu'elle est émise, elle en sort à l'étage de ''Writeback'' si tout se passe bien. Si une exception est détectée, l'étage de ''Writeback'' vide la file d'écriture, ce qui annule les écritures mises en attente, faites à tort. De plus, la file d'écriture est une mémoire FIFO, qui mémorise les écritures dans l'ordre du programme. En effet, les écritures sont ajoutées dans la file d'écriture lorsqu'elles sont émises, et elles sont émises dans l'ordre du programme.
[[File:Store Queue sur un pipeline fixe.png|centre|vignette|upright=2|Write Queue sur un pipeline fixe]]
La file d'écriture garantit que les écritures sont dans l'ordre du programme. Et cette propriété élimine totalement les dépendances WAW. Le fait que les écritures se font à la fin du pipeline élimine quant à elle les dépendances WAR. Il ne reste plus qu'à gérer les dépendances RAW, celles où une lecture lit une donnée écrite par une autre instruction. Une telle situation survient, car les écritures sont mises en attente pendant un certain temps, la donnée écrite peut être lue pendant ce laps de temps. La lecture ne peut pas lire la donnée dans le cache, car la donnée à lire n'y est pas encore enregistrée. Le cas est rare, car il demande de lire une donnée qu'on vient d'écrire, mais il existe. Il se manifeste surtout sur les processeurs avec peu de registre, où des données doivent régulièrement être déplacées temporairement en mémoire RAM.
Pour éviter cela, une solution consiste mettre en attente les lectures si une écriture est en attente. Les lectures sont maintenues dans la file de µops tant que la file d'écriture n'est pas vide. La file de µops mémoire est suivie d'un circuit de détection des dépendances, un '''''scoreboard'' mémoire''' qui est assez simple. L'unité mémoire fournit un bit qui indique si la file d'écriture est vide ou non, ainsi qu'un second bit qui indique qu'elle est vide. Les deux ne sont pas compliqués à générer, ils font partie intégrante de l'interface de toute mémoire FIFO digne de ce nom. Le ''scoreboard'' mémoire utilise ces deux bits pour savoir s'il peut émettre une micro-opération mémoire. Il n'émet pas de lecture tant que la file d'écriture n'est pas vide. Il peut par contre émettre des écritures tant qu'elle n'est pas pleine.
Une meilleure solution serait d'interdire les lectures, mais seulement en cas de dépendance mémoire détectée. Et c'est là qu'intervient la technique du '''''load bypassing'''''. Elle autorise une lecture, si elle n'a aucune dépendance avec les écritures mises en attente dans la file d'écriture. Elle doit être mise en attente dans le cas contraire. Pour l'implémenter, il faut comparer l'adresse à lire avec toutes les adresses dans la file d'écriture. La lecture est exécutée immédiatement si aucune correspondance n'est trouvée, mais elle est mise en attente dans le cas contraire. Pour cela, la file d'écriture est modifiée et devient un mélange entre FIFO et mémoire associative. L'adresse à lire est envoyée à la file d'écriture, et celle-ci vérifie si une écriture est en attente à cette adresse. Si c'est le cas, c'est un succès de ''Write Queue'' : la lecture doit être bloquée. Sinon, c'est un défaut de ''Write Queue'', la lecture accède au cache, là où se trouve la donnée à lire.
Dans le chapitre sur le contournement, nous avons parlé d'une forme de contournement qui corrige ce problème. Les lectures peuvent lire la donnée depuis la file d'écriture si besoin, ce qui renvoie la donnée adéquate. Pour cela, les lectures consultent à la fois le cache et la file d'écriture, comme dit plus haut. L'adresse à lire est envoyée à la file d'écriture, et celle-ci vérifie si une écriture est en attente à cette adresse. Si c'est le cas, c'est un succès de ''Write Queue'', et celle-ci envoie alors la donnée associée à l'étage d'accès mémoire. Sinon, la lecture accède au cache, là où se trouve la donnée à lire. La solution porte le nom de ''Store to load Forwarding'', ou encore de '''réacheminement écriture-vers-lecture'''.
[[File:Store to load forwarding sur un pipeline simple.png|centre|vignette|upright=2|Store to load forwarding sur un pipeline simple]]
La lecture est mise en attente dans l'unité mémoire, vu que c'est elle qui fait les comparaisons d'adresse. Du point de vue extérieur, le ''scoreboard'' mémoire ne voit pas s’il y a eu succès dans la file d'écriture ou non. Il voit juste que l'unité mémoire est occupée et ne peut pas accepter de nouvelle lecture. Elle reste occupée durant quelques cycles, en cas de défaut de ''Write Queue'', le temps de faire la lecture dans le cache. Par contre, en cas de succès de ''Write Queue'', elle reste occupée tant que la lecture n'a pas donné de résultat, ce qui inclue le temps de mise en attente.
Les processeurs haute performance implémentent tous le ''Store to load Forwarding'', mais quelques processeurs basse performance ne le font pas. Ils se contentent du ''load bypassing'', qui offre des performances correctes pour un cout en matériel bien plus modeste. Pour donner un exemple, la micro-architecture Jaguar d'AMD était dans ce cas. Elle a été utilisée sur les consoles de jeu de 8ème génération, comme la Xbox One et la Playstation 4.
Les processeurs haute performance préférent utiliser plus de circuits pour implémenter le ''Store to load Forwarding'', mais l'implémentation n'est souvent pas complète. En effet, l'implémentation est souvent simple et ne prend en compte que des lectures/écritures à des adresses identiques, sans compter que la taille des données doit aussi être identique. Une écriture d'un entier de 32 bit sera contournée et envoyée à une lecture de 32 bits à la même adresse. Mais la lecture lit seulement 16 bits sur les 32 écrits, le contournement peut ne pas marcher. Concrètement, ce cas précis fonctionnera. Mais le cas inverse, avec une lecture lisant plusieurs écritures de taille plus petite, ne marchera pas sur tous les processeurs. Ne parlons pas du cas où les données écrites sont à cheval sur deux lignes de cache.
===Le contournement des adresses mémoire avec une file de µops===
Il faut noter qu'au moment où les instructions LOAD et STORE sont émises, leur adresse n'est pas forcément connue. Sauf en cas d'adressage absolu, mais mettons ce cas particulier de côté, il est assez rare en pratique. Les micro-opérations mémoire sont donc insérées dans la file de µops, mais l'adresse est souvent disponible plus tard. Il faut donc modifier le chemin de données pour que l'adresse soit envoyée à la file de µops mémoire.
Pour simplifier, étudions le cas d'un processeur où les instructions LOAD/STORE ne gèrent que l'adressage indirect à registre, à savoir que l'adresse est lue dans un registre. Les calculs d'adresse sont réalisés par une instruction de calcul séparée de l'instruction LOAD/STORE. Tout calcul d'adresse a lieu dans les unités de calcul entière, les micro-opérations mémoire ne font pas de calcul d'adresse.
Au minimum, il doit y avoir une connexion entre le banc de registre et la file de µops mémoire. Il faut alors attendre que les adresses soient enregistrées dans les registres pour être envoyées à la file de µops mémoire. Cependant, il est possible de profiter du réseau de contournement. L'idée est que l'adresse est accessible avant via le réseau de contournement, avant même d'être enregistrée dans les registres L'idée est que l'adresse est envoyée à l'unité mémoire dès qu'elle sort des unités de calcul. Le gain en performance étant important, tous les processeurs modernes ajoutent un système de contournement qui envoie les adresses à la file de µops mémoire. L'ALU entière calcule l'adresse finale, l'envoie à la file de µops mémoire.
[[File:Contournement pour la file de µops mémoire.png|centre|vignette|upright=2|Contournement pour la file de µops mémoire]]
Maintenant, les instructions LOAD/STORE avec un mode d'adressage indicé, ce qui signifie que l'instruction fait un calcul d'adresse et l'accès mémoire proprement dit. Une solution basique utilise une micro-opération séparée pour le calcul d'adresse et l'accès mémoire. Le calcul d'adresse envoie son résultat à la file de µops mémoire, sans l'enregistrer dans les registres, via contournement. Sans optimisation particulière, les calculs d'adresse sont réalisés par les ALU entières, le processeur n'est pas modifié et reste le même que celui du schéma précédent.
Mais une autre solution utilise des ALU spécialisées dans les calculs d'adresse, appelées des ''Adress Generation Unit'', abrévié AGU. L'avantage est que cela simplifie grandement le réseau de contournement : seules les AGU sont connectées à la file de µops mémoire. Un autre avantage est lié à l'adressage de la destination. En utilisant des opérations entières usuelles, il faudrait préciser que le résultat est à destination de la file de µops mémoire, pour configurer le réseau de contournement. Avec des AGU, les calculs d'adresse sont des micro-opérations différentes des additions et soustraction, elles sont envoyées au AGU spécifiquement, pas besoin de préciser la destination.
[[File:File de µops mémoire avec calcul d'adresse via AGU.png|centre|vignette|upright=2|File de µops mémoire avec calcul d'adresse via AGU]]
Lors de l'émission, la micro-opération mémoire est insérée dans la file de µops mémoire, l'opération de calcul d'adresse est émise dans la fenêtre d'instruction. Et les deux sont exécutés différemment. En effet, un calcul d'adresse est une opération arithmétique, qui est gérée par le système d'exécution dans le désordre. Elle peut s'exécuter dès que ses opérandes sont disponibles. À l'inverse de l'accès mémoire, qui lui doit se faire dans l'ordre du programme. Les adresses sont donc calculées en avance, puis insérées dans la file de µops mémoire. La µops de calcul d'adresse précise où enregistrer son résultat, le résultat est enregistré dans la bonne entrée de la file de µops mémoire.
Les techniques précédentes décodent une instruction LOAD/STORE en deux micro-opérations. Mais il est possible de la décoder en une seule. Le calcul d'adresse est alors réalisé dans l'unité mémoire. Les opérandes du calcul d'adresse sont lues depuis les registres lorsque l'instruction quitte la file de µops mémoire, du moins pour les opérandes qui n'ont pas été contournées. Là, elles sont envoyées à l'unité mémoire, qui fait le calcul d'adresse. Dans les faits, aucun processeur commercial moderne n'utilise cette technique.
[[File:File de µops mémoire avec calcul d'adresse dans l'unité mémoire.png|centre|vignette|upright=2|File de µops mémoire avec calcul d'adresse dans l'unité mémoire]]
===L'optimisation du pseudo-contournement des opérandes mémoire===
Les processeurs modernes implémentent une optimisation concernant les accès mémoire. Cette optimisation a été décrite par Agner Fog dans ses manuels d'optimisation, sous le terme de '''''Mirroring memory operand'''''. Il a documenté son apparition sur les processeurs Zen 2 d'AMD, puis son évolution sur les processeurs Zen 4 et 5. L'optimisation est bizarrement absente de la microarchitecture Zen 3.
Elle se manifeste dans une condition très précise : plusieurs instructions proches accèdent à la même adresse. Les deux instructions ne sont pas forcément consécutives, il peut y avoir une dizaine ou centaine d'instructions entre les deux. Une telle situation survient souvent sur les CPU CISC, et notamment sur les CPU x86, car ils ont peu de registres. les compilateurs font donc beaucoup d'échanges entre pile d'appel et registres, ce qui fait que des données écrites dans la pile sont souvent relue quelques instructions plus tard.
Dans ce cas, la donnée lue/écrite est mémorisée dans un registre interne au processeur et peut être accédée très rapidement. Par exemple, imaginons plusieurs lectures proches à la même adresse, sans écriture entre elles. Dans ce cas, le processeur ne fera qu'une seule lecture en mémoire RAM, les lectures ultérieures liront la donnée depuis le registre interne. Comme autre exemple, imaginons une écriture suivie plus tard par une lecture à la même adresse. Dans ce cas, la donnée sera écrite dans un registre interne, en plus d'être propagée dans le cache ou la RAM, et la lecture ultérieure lira directement le registre interne. Le gain en performance est alors conséquent, comparé à du simple ''store-to-load forwarding''.
Il faut noter que la technique marche aussi pour les opérations ''load-op'', pas seulement pour les instructions d'accès mémoire. Par exemple, prenons une écriture en RAM, suivies quelques instructions plus tard par une opération ''load-op'' à la même adresse. L'écriture écrit à une adresse, qui est lue par l'instruction ''load-op''. L'optimisation s'applique : l'instruction ''load-op'' lira l'opérande mémoire depuis un registre interne du processeur, qui contient une copie de la donnée écrite. Sur les processeurs Zen 2, l'optimisation marchait aussi pour les instructions PSUH et POP, qui lisaient/écrivaient dans la pile d'appel. Mais cette possibilité a été retirée sur les processeurs Zen 5.
La technique détecte que deux instructions accèdent à la même adresse sous certaines conditions bien précises. L'optimisation ne marche que pour les modes d'adressages indicés et indirect, avec ou sans décalages, mais pas en mode absolu ou relatifs. Il faut préciser que l'optimisation ne marche que pour les registres généraux, pas les registres flottants. Les registres doivent être précisés de la même manière dans toutes les instructions. Par exemple, le processeur ne reconnait pas que l'addition des deux registres RCX + RAX est la même que RAX + RCX. Sur les CPU Zen 2 et 4, elle ne marchait que pour des opérandes de 32 et 64 bits, mais les processeurs Zen 5 gèrent des données de 8 et 16 bits aussi. Les décalages étaient limités à 8 bits sur le Zen 2, mais n'a plus de limites sur les Zen 4 et 5.
==Le ''Load Ordering, Store Ordering''==
Intuitivement, on dit qu'exécuter des µops mémoire dans le désordre demande de remplacer la file de µops mémoire par une fenêtre d'instruction. Et si c'est en effet une solution intéressante, nous n'allons pas la voir toute de suite. Nous allons étudier le cas où cette file deµops mémoire est scindée en deux : une file pour les lectures et une autre pour les écritures.
===Les files de µops LOAD et STORE===
Dans le chapitre sur l'exécution dans le désordre, nous avons vu deux possibilités pour implémenter l'exécution dans le désordre. La première utilise une fenêtre d'instruction ou une station de réservation, l'autre utilise plusieurs files de µops séparées. Et cette technique peut parfaitement s'utiliser pour les micro-opérations mémoire. Les micro-opérations mémoire sont réparties dans deux files de µops mémoire séparées, chacun émettant des micro-opérations indépendamment de l'autre. Mais comment répartir les micro-opérations mémoire dans deux files, simplement et sans que cela pose des problèmes de dépendances de données ? La réponse à ces contraintes est toute simple : on utilise une file de µops spécialisée pour les lectures, et une autre pour les écritures.
Les cours d'architecture des ordinateurs nomment souvent ces deux files en utilisant les termes de ''file de STORE'' (''store queue'') et de ''file de LOAD'' (''load queue''). Dans ce qui suit, nous parlerons de '''file de µops LOAD''' et de '''file de µops STORE''', pour bien préciser qu'il s'agit de file de µops. La file de STORE ne doit pas être confondue avec la file d'écriture, qui est appelée ''write queue'' en anglais. Les termes ''store queue'' et ''write queue'' désignent deux choses différentes, mais il y a un risque de confusion pour qui n'est pas assez attentif. D'autant plus que la méthode que nous allons voir a une file d'écriture en plus des deux files de µops mémoire.
En effet, la file d'écriture ne mémorise pas des micro-opérations, juste des écritures déjà émises et en attente. Une entrée dans la file d'écriture contient une adresse et la donnée à écrire. Alors que dans la file de µops STORE, il se peut que l'adresse ou la donnée soient manquantes, car pas encore disponibles. De plus, la file d'écriture met en attente des écritures tant que les instructions précédentes ne sont pas terminées, alors que la file de µops STORE met en attente les micro-opérations avant leur émission. La première attend le feu vert de l'étage de ''Writeback'', l'autre attend le feu vert de l'étage de ''schedule'', d'un ''scoreboard'' amélioré.
Les deux files sont chacune connectées à l'unité mémoire, via un port dédié. L'unité mémoire a donc un port pour les lectures, et un port pour les écritures. Le port d'écriture est relié à la file d'écriture, le port de lecture est directement relié au cache de données. il faut noter qu'une unité mémoire à deux ports n'est pas si bizarre que cela. En effet, le cache de données a lui aussi deux ports, dans la majorité des cas, avec un port de lecture et un port d'écriture.
[[File:Load Ordering, Store Ordering.jpg|centre|vignette|upright=2|Exécution dans le désordre des lectures.]]
Le fait de séparer lectures et écriture a de nombreux avantages. Le premier est qu'une file de µops mémoire unique gâche des transistors. Une écriture a besoin de mémoriser une adresse et une donnée, avec les bits de validité pour les deux. Une lecture n'a besoin que d'une adresse et du bit de validité associé. Avec une file de µops unique, chaque entrée de la file est suffisamment large pour contenir une écriture. Si elle ne contient qu'une lecture, elle est trop large, ce qui sous-utilise les circuits de la file. Avec des files de lecture/écriture séparées, on n'a pas ce problème, chaque file contient juste ce qu'il faut. Il y a donc une économie de transistors.
Cependant, le système est moins flexible. Par exemple, prenons une file de µops mémoire unique, de 16 entrées. Elle peut mémoriser indifféremment 16 lectures, 16 écritures, ou n'importe quel mix d'écriture et de lectures tant qu'il se limite à 16 µops. Avec des files séparées, ce n'est plus le cas. Prenons par exemple le cas avec une file de µops LOAD de 10 éléments et une file de µops STORE de même capacité : impossible d'avoir 16 écritures en attente. Cependant, ce désavantage est quelque peu compensé par l'économie de transistor mentionnée précédemment, qui est réinvestie pour agrandir les files de µops. Typiquement, on agrandit la file de µops LOAD, car les lectures sont plus fréquentes que les écritures dans la majorité des programmes. En ayant une file de µops LOAD assez conséquente, le désavantage est fortement atténué, voire disparait.
===La gestion des dépendances avec des files de µops mémoire séparées===
L'usage de ces deux files de µops mémoire porte le nom de '''''Load Ordering, Store Ordering'''''. Il est intéressant de comparer cette technique avec la précédente, qui utilisait une file de µops mémoire séparée. Avec deux files séparées, les lectures s'exécutent dans l'ordre, les écritures s'exécutent dans l'ordre, mais les lectures peuvent passer avant/après les écritures et réciproquement, tant que l'aliasing le permet. Les écritures peuvent prendre de l'avance sur les lectures, ou réciproquement. Et cette avance est très utile si elle permet d'exécuter des lectures en avance. En comparaison, avec une file de µops unique, ni écritures ni lectures ne peuvent prendre de l'avance. Les performances sont donc améliorées, bien que de peu.
Néanmoins, l'avance des lectures est limitée. Prenons le cas où une lecture doit être mise en attente, car son adresse n'est pas disponible. Avec une file de µops mémoire unique, la lecture bloque toutes les micro-opérations qui suivent. Avec deux files séparées, elle bloque uniquement les lectures, pas les écritures. Les écritures qui suivent la lecture peuvent s'exécuter. Le cas avec une écriture en attente est un peu différent. Une écriture en attente bloquera les écritures suivantes. Mais elle bloquera aussi les lectures postérieures : du point de vue de ces lectures, une écriture précédente a une adresse inconnue, on ne sait pas s'il y a dépendance mémoire avec et on suppose que oui dans le doute. En clair, l'avance des lectures n'a lieu que si les adresses des écritures précédentes sont connues, déjà calculées, sans quoi on ne peut pas détecter les dépendances mémoire.
Le fait que les écritures peuvent être émises en avance fait que la détection des dépendances RAW est différent. L'idée générale reste cependant la même. Lors de l'émission, une lecture doit vérifier ses dépendances avec les écritures antérieures, une écriture doit vérifier ses dépendances avec les lectures ultérieures. Avec une file de µops unique, on est certain que les écritures antérieures sont dans la file d'écriture, dans l'unité mémoire. Et lorsqu'on émet une écriture, on est certain que les lectures ultérieures seront émises après. Avec deux files séparées, ce n'est pas forcément le cas. Si les lectures ont pris de l'avance, une écriture précédente peut être en attente dans la file de µops STORE, alors qu'une lecture ultérieure a été émise. Ce qui complique la détection des dépendances RAW d'adresse. Les détecter demande non seulement de vérifier la file d'écriture, mais aussi la file de µops STORE.
Dans ce qui suit, on suppose que l'unité mémoire incorpore une file d'écriture, ce qui est toujours le cas en pratique. Le fait que les écritures se font dans l'ordre élimine les dépendances WAW et WAR. On suppose aussi que l'unité mémoire implémente le ''réacheminement écriture-vers-lecture'' (''store-to-load forwarding''). Ainsi, si une lecture est émise dans l'unité mémoire, les dépendances RAW avec les écritures déjà émises ne sont pas un problème. Par contre, il faut gérer les dépendances avec les écritures en attente dans la file de µops STORE, dont certaines sont antérieures à la lecture dans l'ordre du programme. Pour cela, il n'y a pas le choix : pour chaque lecture, on vérifie dans la file de µops STORE si elle a une dépendance avec une écriture antérieure. La gestion des dépendances RAW est donc gérée partiellement dans l'unité mémoire et la file de µops STORE.
Pour l'implémenter, il faut comparer l'adresse à lire avec toutes les adresses dans la file de µops STORE. En cas de ''match'', la lecture est bloquée dans la file de lecture. Mais s'il n'y a aucune correspondance, alors la lecture peut s'exécuter. Pour cela, la file de µops STORE est un mélange entre FIFO et mémoire associative. Il faut préciser que la comparaison ne prend en compte que les écritures situées avant la lecture, dans l'ordre du programme. Les deux files de µops doivent donc mémoriser des informations sur l'ordre d'émission. De plus, il arrive que des écritures soient dans la file d'écriture, mais que leur adresse n'ait pas encore été calculée. Dans ce cas, il y a potentiellement dépendance avec la lecture si l'adresse se révèle être la même une fois calculée : on considère que les adresses inconnues sont des correspondances/''match''.
La file de lecture met des lectures en attente, soit car elle attend ses opérandes, soit car elle attend que les lectures précédentes soient terminées, soit car l'aliasing ne lui permet pas. Elle a cependant un défaut : celui de forcer les lectures à s’exécuter dans l'ordre, ce qui est une contrainte inutile. Mais l'avantage est que l'implémentation est très simple. Une file de micro-opération est bien plus simple à implémenter qu'une fenêtre d'instruction.
==Le ''Partial Ordering'' : la ''Load/Store Queue''==
Avec la technique précédente, les lectures s'exécutent dans l'ordre, à savoir que l'on ne peut pas déplacer une lecture avant ou après une autre. Et cette limitation a des conséquences sur la performance de l'exécution dans le désordre. Des lectures sont mises en attente, parce qu'une lecture précédente l'est, alors qu'elles auraient pu s’exécuter avant. Et ce retard sur les lectures se répercute sur les instructions lecture-dépendantes, donc sur le reste du pipeline. Pour éviter cela, la technique du ''Partial Ordering'' permet aux lectures de s'exécuter dans le désordre tant que les conditions d'aliasing sont respectées.
===La ''Load/Store Queue''===
La technique s'implémente en remplaçant les files de µops mémoire par une fenêtre d'instruction spécialisée dans les µops mémoire. Elle est souvent appelée la '''''Load/Store Queue''''', qui est concrètement une station de réservation spécialisée dans les micro-opérations mémoire. En tout cas, nous utiliserons beaucoup l'abréviation LSQ pour parler de la ''Load/Store Queue''.
[[File:Processeur avec émission dans le désordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans le désordre des accès mémoire]]
Une entrée de la ''Load/Store Queue'' contient plusieurs champs : un qui précise si l'opération est une lecture ou une écriture, un pour l'adresse mémoire à lire/écrire, un autre pour la donnée à écrire (qui est vide pour une lecture), un autre pour le registre de destination des lectures (inutile pour les écritures). Les deux derniers champs sont souvent fusionnés en un seul, qui est interprété différemment selon que l'instruction est une lecture ou une écriture.
[[File:Load Store Queue unifiée.png|centre|vignette|upright=2.5|''Load/Store Queue''.]]
Lorsqu'une micro-opération mémoire est émise, après renommage de registres, elle est insérée dans la ''Load/Store Queue''. Si l'adresse à lire/écrire est disponible, elle est immédiatement lue depuis les registres, idem pour une éventuelle donnée à écrire. Sinon, la micro-opération attend dans la LSQ que son adresse soit calculée par l'ALU entière. Pour gérer ce cas, elle contient des bits de validité pour chaque champ qui indiquent si l'adresse d'accès est connue, et si la donnée à écrire est disponible pour une écriture.
Il faut noter que tout ce qui a été dit plus haut à propos du contournement des adresses vaut aussi avec une ''Load/Store Queue'', à un détail près. Pour détecter les dépendances, la LSQ doit comparer les adresses à lire/écrire, ce qui fait qu'elles doivent avoir été calculées avant d'entrer dans la LSQ. En clair, il n'est pas possible de faire les calculs d'adresse dans l'unité mémoire, juste avant d’accéder au cache. Les unités de calcul d'adresse sont réalisés dans une ALU entière ou une AGU spécialisée.
Lorsqu'une instruction mémoire est émise, elle est décodée en deux micro-opérations : une pour le calcul d'adresse, une autre pour l'accès mémoire proprement dit. Le calcul d'adresse est réalisé soit dans une ALU entière, soit dans une AGU spécialisée. L'accès mémoire attend dans la ''Load/Store Queue'' que l'adresse soit calculée. Une fois l'adresse calculée, elle est envoyée à la ''Load/Store Queue'' et est enregistrée dans l'entrée adéquate. Reste qu'il faut envoyer l'adresse des ALU à la LSQ. Pour cela, on réutilise le réseau de contournement du processeur.
L'implémentation la plus simple effectue les calculs d'adresse dans les ALU entières. La sortie de toutes les ALU entière est reliée à la LSQ.
[[File:Load-Store Queue.png|centre|vignette|upright=2|Load-Store Queue]]
Les processeurs modernes préfèrent utiliser des unités de calcul d'adresse dédiées, afin de simplifier le réseau de contournement.
[[File:ALU et LSQ.png|centre|vignette|upright=1.5|ALU et LSQ]]
===La détection des dépendances mémoire===
La ''Load/Store Queue'' est couplée à un système de détection des dépendances mémoire, qui détermine quelles instructions peuvent s'exécuter sans problème. Et ce système de détection des dépendances est bien différent de celui d'une station de réservation normale. Les ''Load/Store Queue'' comparent non pas des noms de registres, mais des adresses/opérandes. Chaque micro-opération en attente compare son adresse avec celle de toutes les micro-opérations précédentes. Ou presque, la comparaison ne doit tenir compte que des écritures, et de celles situées avant dans l'ordre du programme. La ''Load/Store Queue'' doit donc mémoriser les instructions dans l'ordre du programme, tout en faisant des comparaisons d'adresse. Ça en fait une sorte hybride entre mémoire associative et mémoire FIFO.
Nous avions vu dans le chapitre "L'exécution dans le désordre" qu'il y a plusieurs manières pour implémenter une station de réservation : avec une mémoire associative, avec préplanification, avec des matrices de dépendances. Le problème est qu'on ne peut pas utiliser une mémoire associative pour la LSQ, qui utilise des comparateurs pour comparer les noms de registres/adresses. En effet, la détection des dépendances mémoire demande de faire beaucoup de comparaisons : chaque adresse est comparé à toutes les autres. Et les adresses faisant facilement 32 à 64 bits, les comparateurs sont bien plus imposants que ceux qui comparent des noms de registres de 5-10 bits. Le cout en matériel serait trop élevé, de même que le temps de calcul des dépendances. À la place, on utilise des matrices de dépendances.
La détection des dépendances mémoire utilise précisément deux matrices de ce type : une matrice de disponibilité, et une matrice de dépendance. Ces matrices forment une espèce de tableau carré, organisé en lignes et en colonnes. L'instruction dans la énième entrée se voit attribuer la énième ligne et la énième colonne. À l'intersection d'une ligne et d'une colonne, on trouve un bit qui indique si l'instruction de la ligne et celle de la colonne ont une dépendance.
La '''matrice de disponibilité''' permet de bloquer l'exécution des lectures/écritures tant qu'une instruction précédente n'a pas calculé son adresse, tant qu'on ne peut pas vérifier les dépendances. Lorsqu'une instruction est ajoutée dans le LSB, elle met tous les bits de la colonne attribuée à 1 si l'adresse à lire/écrire est inconnue, puis les met à 0 une fois l'adresse calculée. Une instruction ne peut pas démarrer si, sur la ligne qui lui est attribuée, se trouve un 1. Plus précisément, si on trouve un 1 pour les instructions plus anciennes, ce qui fait qu'une partie de la matrice est ignorée. Les bits notés X dans le tableau suivant sont ignorés, car ils précisent la disponibilité des opérandes d'un opérande ultérieure, ce qui est inutile. En clair, les calculs d'adresse modifient les colonnes, et on vérifie les lignes pour autoriser l'exécution.
{|class="wikitable"
|-
! Instructions mémoire dans la LSB,
triées de la plus ancienne à la plus jeune
! 0 !! 1 !! 2 !! 3 !! 4 !! 5 !! 6
|-
! 0
| 1 || X || X || X || X || X || X
|-
! 1
| 0 || 0 || X || X || X || X || X
|-
! 2
| 0 || 1 || 0 || X || X || X || X
|-
! 3
| 0 || 1 || 1 || 1 || X || X || X
|-
! 4
| 0 || 0 || 0 || 0 || 1 || X || X
|-
! 5
| 0 || 0 || 1 || 0 || 0 || 1 || X
|-
! 6
| 1 || 1 || 1 || 1 || 1 || 1 || 0
|}
Les '''matrices de dépendances''' fonctionnent sur le même principe, mais déterminent les dépendances mémoire. Lorsque le processeur a calculé une adresse, il la compare avec les adresses à lire/écrire déjà dans la LSB. Il met alors à jour les bits de la colonne de la matrice de dépendance : le bit à l'intersection d'une ligne et d'une colonne est mis à 0 si les deux instructions n'ont pas de dépendances, à 1 si elles en ont une. Une instruction mémoire est exécutée si tous les bits de sa ligne sont à 0. Là encore, on ne tient compte que des bits pour les instructions plus anciennes, la matrice a encore une fois une forme triangulaire, comme pour la matrice de disponibilité.
Une optimisation scinde la ''Load/Store Queue'' en deux structures, aux rôles légèrement différents. La première gère la disponibilité des opérandes, la seconde gère seulement les dépendances mémoire. La première est une station de réservation où les instructions sont mises en attente tant que leurs opérandes ne sont pas prêts. La seconde met en attente des micro-opérations dont les opérandes sont prêts, mais qui doivent attendre pour une autre raison : dépendance mémoire, cache occupé, etc.
L'avantage est qu'on remplace une grosse station de réservation par deux plus petites, mais l'intérêt est surtout relié au système de détection des dépendances. Sans cette optimisation, on a deux matrices énormes reliées à une ''Load/Store Queue'' unique. Avec la séparation, la matrice de disponibilité est reliée uniquement à la première station de réservation, alors que la matrice de dépendance est reliée uniquement à l'autre. Les deux matrices sont donc plus petites, car reliées à deux stations de réservation mémoire plus petites.
==L'exécution spéculative des accès mémoires==
Pour rappel, le but de la désambiguïsation mémoire est d'exécuter les lectures le plus tôt possible. Les données lues sont en effet, le départ d'une série d'instructions dépendantes, généralement des instructions arithmétiques, mais parfois d'autres lectures. Quand on manipule des structures de données, il n'est pas rare qu'une lecture lise un pointeur-adresse utilisé par une autre lecture.
Les techniques de désambiguïsation mémoire que nous avons vu précédemment sont dites conservatrices, à savoir qu'elles ne changent pas l'ordre de deux instructions mémoire si elles ont une dépendance d'adresse. Elles exécutent une lecture le plus tôt possible, mais seulement si on sait que celle-ci n'aliase pas d'écritures précédentes. Et cela demande de connaitre les adresses des écritures précédentes dans l'ordre du programme. Si une lecture est précédée par une ou plusieurs écritures, les adresses à écrire doivent être connues. Il se peut en effet qu’une écriture dont on ne connait pas l'adresse aliase la lecture. La garantie est liée à la présence de la file de µops mémoire ou la file de µops STORE. Si une seule écriture a une adresse inconnue, elle reste dans la file de µops mémoire et bloque l'émission de toutes les autres micro-opérations mémoire, lecture comme écriture.
Mais il existe des techniques dites spéculatives, qui modifient l'ordre des accès mémoire sans se soucier de leurs dépendances. Elles émettent les lectures dès que leurs opérandes sont prêts, sans se préoccuper de l'aliasing avec une écriture précédente. Pour éviter tout problème, le processeur vérifie si aucune dépendance mémoire n'est violée et corrige le tir dès qu'une violation est détectée. Le processeur vérifie s'il a fait une erreur de prédiction, en vérifiant les adresses à écrire ou lire. En cas d'erreur, le processeur fait comme lors d'une prédiction de branchement ratée : il remet le pipeline en ordre et ré-exécute les accès mémoire dans le bon ordre. Il s'agit d'un cas particulier d’'''exécution spéculative''', au même titre que la prédiction de branchement.
Toutes les techniques de ce genre exécutent des lectures dans le désordre. L'avantage est que cela permet d'exécuter les lectures en avance. Vu que la donnée lue est l'opérande d'autres instructions, les exécuter en avance fait prendre de l'avance à un paquet d'instructions. Mais il faut comparer cet avantage au risque de mauvaise prédiction. Et aussi bizarre que cela puisse paraître, cela donne de bons résultats. Il faut dire que les situations où une lecture relit une donnée tout juste écrite sont rares : même pas 2 à 3 % des lectures. Les dépendances mémoire sont donc très rares, les cas d'aliasing sont vraiment limités. En termes de cout en transistors, le hardware qui vérifie les dépendances mémoire reste globalement le même à peu de choses près, il est juste déplacé dans le pipeline et adapté à sa nouvelle fonction.
===La file de lectures===
La première technique que nous allons voir reprend la technique du ''Load Ordering, Store Ordering'', avec ses deux files de µops de lecture et d'écriture. Elle exécute les lectures sans se préoccuper des dépendances avec les écritures. Mais le processeur vérifie qu'aucune violation de dépendance n'a eu lieu après un certain temps. Une violation de dépendance peut avoir lieu pour n'importe quelle instruction précédant la lecture dans l'ordre du programme. En clair, le processeur doit conserver les lectures spéculatives tant que les instructions précédentes ne sont pas terminées. Pour cela, il faut ajouter une nouvelle file d'attente, qui mémorise les lectures terminées. Nous l’appellerons la file de vérification des lectures, ou encore la '''file de lectures'''.
Elle fonctionne sur le même principe que la file d'écriture et les deux font partie de l'unité mémoire. Les deux visent à remettre les lectures/écritures dans l'ordre du programme, afin que les instructions suivant une lecture/écriture fautive soient annulées. Les lectures sont insérées dans la file de lecture à l'émission, quand elles quittent la file de µops mémoire/LSQ, ce qui est la même chose avec les écritures pour la file d'écriture. Une différence très importante est que les écritures sont retardées alors que les lectures s'exécutent immédiatement. D'ailleurs, la file de lecture ne sert pas pour le contournement : les données lues sont immédiatement envoyées au chemin de données, pas besoin de les contourner ultérieurement. Alors que la file d'écriture est utilisée pour le contournement en cas de dépendances RAW.
Les lectures sortent de la file µops LOAD dès que leurs adresses sont calculées, sans tenir compte des dépendances mémoires. Elles sont alors ajoutées à la file de lectures et s'exécutent immédiatement, de manière spéculative. Pour vérifier qu'il n'y a pas d'erreur de spéculation, le processeur conserve l'adresse de la lecture effectuée dans la file de lectures. Les lectures quittent la file de lecture quand toutes les instructions précédentes dans l'ordre du programme sont terminées, à savoir tant quand elles ont quitté le tampon de réordonnancement/ROB. Notons que la file de vérification des lectures contient les lectures dans l'ordre du programme. En effet, les lectures sont accumulées en sortant de la file de µops LOAD, où elles sont triées dans l'ordre du programme. Et cela permet de gérer les erreurs de prédiction parfaitement, en annulant les instructions suivant une erreur de prédiction.
[[File:Spéculation avec une file de lecture.jpg|centre|vignette|upright=2.5|Spéculation avec une file de lecture.]]
Reste à détecter les erreurs de prédiction, ce qui peut être fait avec deux méthodes.
Avec la première, les violations de dépendances mémoire sont faites à chaque écriture. Précisément, la détection a lieu quand une écriture est sur le point de quitter la file d'écriture. L'unité mémoire récupère alors l'adresse d'écriture et la compare avec toutes les lectures dans la file de vérification des lectures. Si aucune correspondance n'est trouvé, c'est que les lectures spéculatives n'ont accédé à l'adresse d'écriture. Mais s’il y a correspondance, c'est qu'une lecture a lu l'adresse avant qu'elle soit écrite. Pour cela, la file de vérification des lectures est un mélange entre FIFO et cache/mémoire associative. La vérification fait cependant gaffe à ne prendre en compte que les lectures situées après l'écriture dans l'ordre du programme (seulement ces instructions sont capables de générer une dépendance RAW).
Avec la seconde méthode, les violations de dépendance mémoire sont détectées au moment où la lecture quitte à la file de lectures et le ROB. Mais cela demande que la donnée lue soit recopiée dans la file de lecture. Quand la lecture quitte la file de lecture et le ROB, la lecture est ré-exécutée et la donnée relue. Le processeur compare alors la donnée lue à ce moment avec la donnée mémorisée dans la file de lecture. Si la donnée est différente, alors il y a eu erreur de violation de dépendance mémoire. Évidemment, relire une donnée n'est pas gratuit. Il est possible de limiter son impact en performance en dédiant un port de lecture sur le cache rien que pour ça. Mais le cout en circuit est comparable à l'usage d'une FIFO associative de la technique précédente. Mais quoi qu'il en soit, le cache doit permettre des lectures en un seul cycle, sans quoi la méthode ne marche pas vraiment.
===La prédiction de dépendances mémoires===
Certains processeurs essayent de prédire si deux accès mémoires sont dépendants : ils incorporent une unité qui va fonctionner comme une unité de prédiction de branchement, à la différence qu'elle prédit les dépendances mémoires. Si cette unité prédit que deux accès mémoires sont indépendants, le processeur les exécute dans le désordre, et les exécute dans l'ordre du programme dans le cas contraire. Il faut prendre en compte les erreurs de prédiction, ce qui est fait comme pour les mauvaises prédictions de branchement : on vide le pipeline.
Beaucoup de techniques de prédiction des dépendances mémoire ont été inventées et en faire une revue exhaustive serait long et fastidieux. On pourrait citer les ensembles colorés (''color sets''), et bien d'autres techniques. Mais nous n'allons parler que des techniques les plus élémentaires. Quoi qu’il en soit, la recherche sur le sujet est assez riche, comme toujours quand il s'agit d'architecture des ordinateurs.
On peut améliorer cette technique en mémorisant les instructions qui ont causé une mauvaise prédiction de dépendance mémoire. La prochaine fois qu'on exécute ces instructions, on sait qu'il y a de grandes chances qu'il se produise une erreur de prédiction, ce qui pousse à ne pas les exécuter de manière spéculative. On peut pour cela réutiliser les techniques de prédiction de branchement, tels des compteurs à saturations, mis à jour en cas d'erreurs de prédiction de dépendances mémoire. Dans tous les cas, on trouve un cache, équivalent au ''branch target buffer'', qui mémorise les instructions fautives (leur ''Program Counter''), avec d'autres informations comme les adresses des instructions dépendantes. La première classe de techniques du genre consiste à mémoriser les lectures qui ont causé une violation de dépendance, tandis que l'autre ne mémorise que les écritures. Cette dernière est l'approche utilisée par le '''cache de barrières d’écriture''' (store barrier cache).
Enfin, nous allons conclure avec une dernière technique : celle des '''ensembles d’écritures''' (store sets). Cette technique est capable de gérer le cas où une lecture dépend de plusieurs écritures : le processeur mémorise l'ensemble des écritures avec lesquelles la lecture a déjà eu une dépendance. Cet ensemble d'écritures associées à une lecture est appelé tout simplement un ensemble d’écritures, et reçoit un identifiant (un nombre) par le processeur. Cette technique demande d'utiliser deux tables :
* une qui assigne un identifiant d’ensemble d’écritures à l'instruction en cours ;
* une autre qui mémorise les ensembles d’écritures sous la forme d'une liste chainée : le début de la liste correspond à l'écriture la plus ancienne de l’ensemble. Cela permet d'obtenir l'écriture la plus ancienne dans cet ensemble d’écritures directement.
Quand le processeur détecte une violation de dépendance entre une lecture et une écriture, elle ajoute l'écriture dans l’ensemble d’écritures adéquat. Le déroulement d'une lecture demande d'accéder à la première table pour récupérer l'identifiant de l’ensemble d’écritures, et l'utiliser pour adresser la seconde table. S'il y a dépendance, cet accès renvoie l'écriture fautive en cas de dépendance. Quand une écriture est envoyée à l'unité mémoire, celle-ci va accéder à la table de correspondances de la même manière qu'une lecture, et va récupérer l'identifiant de l’ensemble d’écritures auquel elle appartient, identifiant qui est utilisé pour vérifier s'il y a une écriture en cours d’exécution. Si c'est le cas, cela signifie que l'écriture en cours d’exécution doit s’exécuter avant l'écriture qui a consulté la table : cette dernière est mise en attente.
==La prédiction d'adresse et de valeur==
D'autres techniques de prédiction mémoire plus élaborées que les précédentes existent. Il s'agit de la prédiction d'adresse et de la prédiction de valeur. Leur nom trahit ce qu'elles font. La première technique tente de prédire l'adresse d'une lecture/écriture, alors que la seconde tente carrément de prédire quelle sera la donnée lue.
Il y a bien de la recherche académique sur le sujet, mais il s'agit de techniques assez osées, dont on voit mal comment elles pourraient fonctionner. L'efficacité de cette technique a été étudiée grâce à des simulateurs. Suivant les études ou les programmes, on trouve des résultats qui varient pas mal. Dans certains cas, la performance baisse un peu, et dans d'autres, on peut avoir des gains de plus de 10% ! Mais dans des cas normaux, on trouve des gains de 4-5% environ. Ce qui est tout de même pas mal à l'heure actuelle.
Cependant, en Janvier 2025, à l'heure où j'écris ces lignes, il a été découvert la première utilisation de ces deux techniques dans un processeur commercial. La découverte fait suite à la publication de deux vulnérabilités matérielles sur les CPU Apple M2 et M4 et quelques CPU ARM, qui font justement usage de ces deux techniques. Les deux vulnérabilités, appelées SLAP et FLOP, sont assez similaires aux attaques Spectre et Meltdown, sauf qu'elles attaquent non pas la prédiction de branchement, mais la prédiction d'adresse et de valeur du CPU.
===La prédiction d'adresse===
La prédiction d'adresse tente de prédire l'adresse d'une lecture à l'avance, ce qui permet de lancer les lectures en avance. Si la prédiction est correcte, on gagne quelques cycles qui permet aux unités de calcul de faire leurs calculs plus en avance, sans compter que les mécanismes de désambiguïsation mémoire fonctionnent plus efficacement. Si jamais cette adresse est connue plus tôt, on détecte les dépendances plus rapidement et on peut agir en conséquence.
Des variantes de ces unités de prédiction d'adresse sont utilisées pour précharger des données depuis le cache de donnée. Et leur efficacité est assez bonne, voire excellente dans certains cas. La différence est que la prédiction d'adresse est utilisée pour prédire les adresses à lire depuis le cache vers les registres, ce qui est le sujet de cette sous-partie.
La prédiction d'adresse est réalisée par une sorte de cache, qui associe à chaque instruction la prochaine adresse à lire. Nous appellerons ce cache la '''table de prédiction d'adresse'''. Pour faire l'association instruction <-> adresse lue/écrite, le tag du cache utilise l'adresse de l'instruction (le ''program Counter''), alors que la ligne de cache associée contient l'adresse prédite. Son contenu est utilisé pour faire une prédiction. Lorsqu'une lecture est émise, la table est consultée pour récupérer en avance l'adresse à lire, avant que celle-ci soit calculée par l'ALU. La lecture est alors exécutée en avance, avant que la vraie adresse soit connue, en utilisant l'adresse prédite.
Reste qu'il faut des circuits autour de cette table de prédiction d'adresse, pour déterminer si la prédiction est correcte, ainsi que pour remplir cette table avec une adresse prédite. Pour cela, il y a un circuit de prédiction d'adresse dédié. Il récupère les instructions qui quittent le ROB ou la file des lectures mémoire, afin de vérifier les lectures qui viennent de se terminer/''commit'' et qui quittent le pipeline. Il peut contenir une '''table d'apprentissage''', qui mémorise des informations nécessaires pour faire des prédictions d'adresse. Par exemple, un historique des dernières adresses lues pour les lectures les plus récentes, ou un historique des adresses des lectures. La table est aussi utilisée pour déterminer si les prédictions effectuées étaient correctes ou non. Pour cela, il est possible de réutiliser les techniques vues dans le chapitre sur la prédiction de branchement, notamment des compteurs à saturation.
Pour la prédiction de branchement et les autres formes de prédiction mémoire, les table d'apprentissage et pour les instructions/adresses sont fusionnées en une seule. Mais ici, il est nécessaire de les séparer pour une raison assez particulière : éliminer la pollution du cache dans la table de prédiction d'adresse. On ne peut pas allouer une ligne de cache pour chaque nouvelle instruction de lecture exécutée. Seule une minorité des lectures profitent de la prédiction d'adresse. Il est nécessaire de filtrer les lectures pour ne conserver que celles qui profitent de la prédiction d'adresse, ce qui est possible plus facilement en séparant la table d'apprentissage et les compteurs à saturation de la table de prédiction d'adresse.
Une première méthode consiste à prédire que la lecture lit toujours la même adresse, ce qui lui vaut le nom de '''prédiction d'adresse constante'''. On pourrait se demander pourquoi une telle situation surviendrait. La raison est qu'il arrive que les compilateurs n'arrivent pas à gérer les accès mémoires efficacement. Dans certaines situations un peu compliquées impliquant des pointeurs ou des références, divers phénomènes complexes d'''aliasing'' des pointeurs peuvent générer des relectures intempestives de données en mémoire. Cela peut aussi arriver lorsqu'on compile du code qui provient de plusieurs librairies, ou quand du code lit régulièrement des constantes en mémoire ROM.
La prédiction d'adresse constante ajoute un circuit qui analyse les dernières lectures, une fois qu'elles quittent le ROB, qu'elles ''commit''. Si, pour une instruction de lecture, l'adresse lue est toujours la même, alors l'instruction est ajoutée dans la table de prédiction d'adresse. Vu que les instructions qui accèdent toujours à la même adresse sont rares, il est préférable d'initialiser les compteurs à saturation de façon à ce qu'ils disent que toute nouvelle instruction change d'adresse. Il s'agit d'une méthode simple mais peu efficace, car cette situation est rare.
Une autre méthode détecte les accès en enjambées, là encore quand les lectures quittent le ROB. Nous l’appellerons la '''prédiction d'adresse en enjambées'''. De tels accès surviennent régulièrement quand on accède des tableaux ou d'autres structures de données semblables, ce qui rend la technique très intéressante. Implémenter cette technique est facile : il suffit d'ajouter un circuit qui détecte les accès en enjambées, au lieu de celui qui détecte les accès constants. À chaque fois qu'une lecture entraine un succès de cache dans la table de prédiction d'adresse, l'enjambée est ajoutée à l'adresse contenue dans la table de prédiction d'adresse, l'ancienne adresse prédite est remplacée par celle avec l'enjambée d'ajoutée.
Sur les Apple M2-M4, la prédiction d'adresse gére aussi bien les adresses constantes que les accès en enjambée. Le brevet qui décrit l'implémentation de l'unité de prédiction d'adresse et de valeur est potentiellement le suivant : [https://patents.google.com/patent/US11829763B2/en Early load execution via constant address and stride prediction]. En théorie, il est possible de faire mieux en repérant des accès mémoires qui se répètent de façon régulière et cyclique, même s'ils n'ont aucune enjambée. Ce genre d'accès se trouve assez souvent lorsque l'on manipule des listes chainées ou des structures de données assez irrégulières comme des arbres, des graphes, etc. Mais il n'y a pas encore de CPU qui implémente de telle optimisation.
===La prédiction de valeur : prédire la donnée lue===
Les techniques de '''prédiction de valeur''' (''Value Prediction'') consistent à prédire quelle est la donnée qui sera lue par une instruction de lecture. Oui, vous avez bien lu : le processeur est capable de parier sur la valeur qui sera chargée depuis la mémoire, de prédire si cette valeur vaut 0, 1, 1024, etc. Une fois son pari fait, il exécute les instructions du programme avec la valeur qu'il a pariée de façon spéculative. Si le pari est correct, le CPU continue l’exécution. Sinon, il est obligé de vider le pipeline et de recommencer avec la bonne valeur.
Au premier abord, cette technique semble franchement mal partie tellement les chances de se tromper sont tellement énormes ! Mais le fait est que, dans certains cas, il est possible de spéculer correctement. Là encore, les techniques les plus simples fonctionnent dans deux cas. Le premier est celui où une donnée est relue à l'identique à chaque fois que la lecture est exécutée. Le second est celui où la donnée est incrémentée/décrémentée d'une constante qui est toujours la même d'une exécution de la lecture à l'autre. Une sorte d'enjambée constante, mais pour les données.
Nous allons nous concentrer sur le premier cas, celui où une donnée est relue plusieurs fois sans être modifiée entre temps. L'idée de base est qu'une instruction de lecture qui s'exécute plusieurs fois de suite à la même adresse renvoie la même valeur à chaque fois. C'est parfaitement possible s’il n'y a eu aucune écriture à cette adresse entre-temps, d'où le fait qu'il faille surveiller si les prédictions constantes sont correctes.
On peut se demander quelles sont les raisons qui font qu'une instruction de lecture renvoie la même valeur à chaque fois. Après tout, autant lire une seule fois la donnée et la garder dans un registre une bonne fois pour toutes ! Mais dans la réalité, on fait face à quelques limites, qui seront détaillées dans la liste suivante. La première est que le manque de registre fait que certaines données sont conservées en RAM et sont relues fréquemment. Cela arrive quand on stocke des constantes en mémoire. Par exemple, sur les processeurs x86, les constantes flottantes ne peuvent pas être intégrées dans nos instructions via le mode d'adressage immédiat. À la place, on les stocke en mémoire et on les charge dans les registres à chaque fois qu'on en a besoin.
L'implémentation est similaire à la prédiction d'adresse, si ce n'est que c'est la donnée lue qui est prédite. Là encore, on a une '''table de prédiction de donnée lue''', consultée lorsqu'une lecture est émise. La table de prédiction de la donnée lue est une mémoire cache qui associe le ''Program Counter'' de la lecture à la donnée prédite, éventuellement couplé à des compteurs à saturation. Elle est consultée à chaque fois qu'une lecture est émise, la donnée adéquate est récupérée lors d'un succès de cache. On trouve aussi un circuit qui insère ou retire des instructions de la table de prédiction de donnée lue. Elle détecte les lectures prédictibles, couplé à une table d’apprentissage, et quelques circuits annexes liés au ROB ou aux files de lectures. Elle contient au minimum des compteurs à saturations, associés chacun à une instruction (son ''program counter'' pour être précis).
==La désambiguïsation mémoire a des effets de bord==
La désambiguïsation mémoire a une caractéristique que l'exécution dans le désordre simple n'a pas : elle a des effets qui débordent sur la mémoire. Il est possible de savoir si un processeur fait de la désambiguïsation mémoire en regardant le bus mémoire. On s’aperçoit alors que les lectures se font dans un ordre différent de celui du programme. À l'opposé, l'exécution dans le désordre n'a pas d'effets observables de ce genre. Elle améliore la performance, elle modifie les durées observables des instructions, mais cela se limite à des questions de ''timings''. Le seul moyen de savoir si un processeur fait de l'exécution dans le désordre est de mesurer les latences/durées de chaque instruction. Mais aucun état observable au-delà des ''timings'' n'est modifié. Pas avec la désambiguïsation mémoire.
===La consistance mémoire===
Le fait que la désambiguïsation mémoire ait des effets observables ne pose aucun problème avec un seul processeur, ca elle est conçue pour. Par contre, les techniques de désambiguïsation mémoire peuvent poser des problèmes avec plusieurs processeurs. Il arrive que des programmes partagent une même portion de mémoire, et lisent/écrivent dedans. Avec plusieurs processeurs, chaque processeur effectue la désambiguïsation mémoire dans son coin sans se préoccuper des autres. Les opérations d'écriture peuvent être mises dans le désordre du point de vue des autres processeurs, et les lectures effectuées par les autres processeurs peuvent alors renvoyer de vielles données.
Un exemple classique est celui de deux cœurs qui partagent le même cache ou la même mémoire, mais qui utilisent chacun une file d'écriture (''store buffer''). Imaginons que le premier cœur effectue une écriture, puis une lecture, à des adresses différentes. L'écriture est retardée grâce au tampon d'écriture, alors que la lecture lit directement le cache. Pour le premier processeur, l'écriture s'est réalisée avant la lecture, car pour lui, la fin de l'écriture signifie écrire dans le tampon d'écriture. Mais pour le second cœur, l'ordre s'est inversé : il voit la lecture dans le cache, puis l'écriture dans le cache. La raison est qu'il n'a pas connaissance du contenu du tampon d'écriture de l'autre cœur.
Un autre exemple : les techniques de prédiction d'adresse et de valeur posent des problèmes avec les dépendances RAW. Elles permettent à une instruction mémoire de s'exécuter avant une autre, malgré la présence d'une dépendance RAW entre les deux. L'ordre entre les deux instructions change, alors que les autres processeurs ne devraient pas le voir. Et dans les faits, les processeurs qui incorporent ces techniques ont des modèles de consistance mémoire qui ne permettent pas ces optimisations en cas de dépendances RAW. Les deux techniques doivent idéalement être désactivées pour les instructions mémoire qui ont des dépendances RAW.
Les programmeurs doivent tenir compte de ce genre de situations, dans des cas assez rares et liés à la programmation bas niveau. Pour cela, ils doivent savoir comment le processeur réorganise les accès mémoire. Concrètement, chaque processeur définit un '''modèle de consistance mémoire''' qui indique quelles optimisations sont activées et non. Il s'agit d'une sorte de contrat entre le logiciel et le matériel : les programmeurs savent comment le processeur va réorganiser les lectures et écritures et peuvent coder leurs programmes en fonction. L'implémentation du système de consistance mémoire dépend grandement de l'architecture, mais des modèles ont été standardisés afin de permettre aux programmeurs de savoir où placer les barrières mémoires. Il s'agit de vrais standards, formalisés, parfois mathématiquement, que des chercheurs étudient sur un plan mathématique et algorithmique.
Le modèle préféré des programmeurs est la '''consistance séquentielle''', où les différents processeurs effectuent des accès mémoire dans l'ordre du programme et les accès mémoire simultanés sont interdits. La première contrainte interdit l'usage de mécanismes de désambiguïsation mémoire, la seconde interdit l'usage de caches non-bloquants. La consistance séquentielle a donc un cout en performance assez important, ce qui fait que les processeurs modernes n'utilisent pas la consistance séquentielle.
Un modèle plus flexible est le '''''total store ordering''''', qui autorise la présence d'une file d'écriture et l'usage de caches non-bloquants pour les lectures (interdit d'avoir plusieurs écritures simultanées). C'est plus ou moins celui utilisé par les processeurs x86, comme on le verra plus bas. Il existe des modèles de consistance mémoire plus relâchés, appelés des '''modèles de consistance faible''', qui autorisent diverses optimisations de spéculation mémoire, la combinaison d'écriture (fusionner deux écritures à la même adresse en une seule), etc. Ils sont supportés sur les architectures ARM et POWER.
===Les barrières mémoire===
Le changement d'ordre des lectures et écritures peut poser occasionnellement des problèmes avec la programmation pour les architectures multi-processeur ou multicœurs. Il existe des morceaux de code qui ne marchent que si la consistance séquentielle est respectée et il faut faire en sorte qu'ils marchent sur des processeurs avec un modèle de consistance mémoire différent. Pour cela, les processeurs incorporent des instructions appelées '''barrières mémoires''' ou '''''Fences'''''. Elles forcent le processeur à terminer tous les accès mémoire qui précédent dans l'ordre du programme. Certains processeurs possèdent une barrière mémoire pour les lectures et une autre pour les écritures. D'autres processeurs ajoutent des barrières mémoires dédiées à des cas particuliers. Elles permettent de forcer les accès à des données partagées à dérouler correctement.
[[File:Fences.png|centre|vignette|upright=2|Fences.]]
Leur implémentation est assez simple : elles empêchent l'émission de toute nouvelle instruction mémoire, tant qu'une condition précise n'est pas réunie. Par exemple, la barrière mémoire pour les écritures attend que la file d'écriture soit vidée avant de démarrer le moindre accès mémoire.
Un compilateur peut placer les barrières mémoires au bon endroit, avec un peu d'aide du programmeur. Certains langages de programmation permettent d'indiquer au compilateur qu'une donnée doit toujours être lue depuis la mémoire RAM, via le mot-clé volatile. C'est très utile pour préciser que cette donnée est potentiellement partagée par plusieurs processeurs ou manipulable par des périphériques. Les compilateurs peuvent placer des barrières mémoires lors des lectures ou écritures sur ces variables, mais ce n'est pas une obligation.
Il arrive aussi que le programmeur doive manipuler explicitement des barrières mémoires. Utiliser l'assembleur est alors une possibilité, mais qui est rarement exploitée, pour des raisons de portabilité. Pour limiter la casse, certains systèmes d'exploitations ou compilateurs peuvent aussi fournir des barrières mémoires explicites, encapsulées dans des bibliothèques ou cachées dans certaines fonctions.
===La modèle de consistance des processeurs x86===
Après avoir vu la théorie, passons maintenant à la pratique. Dans cette partie, on va voir les modèles de consistances utilisés sur les processeurs x86, ceux qu'on retrouve dans les PC actuels. Le modèle de consistance des processeurs x86 a varié au cours de l'existence de l'architecture : un vulgaire 486DX n'a pas le même modèle de consistance qu'un Core 2 duo, par exemple. Ils ont évolués au cours du temps, avec l'arrivée de nouvelles optimisations. De plus, leur formalisation a été progressive, et s'est faite au fur et à mesure.
En général, les modèles de consistance des processeurs x86 ont toujours étés assez restrictifs, si on les compare aux autres processeurs. Il s'agit de modèles de type ''total store ordering'' (TSO), au moins dans les grandes lignes. Ils permettent donc d'effectuer certaines lectures en avance, soit parce que les lectures se font à des adresses différentes, soit par utilisation de réacheminement écriture vers lecture. Les possibilités d'exécution en avance des lectures varient suivant le processeur, elles deviennent de plus en plus performantes avec le temps.
Le premier modèle de consistance est apparu sur les premiers processeurs x86 et est resté en place sur tous les processeurs de marque Pentium. Les processeurs de l'époque n'incorporaient pas beaucoup d'optimisations, le budget en transistor n'était pas assez conséquent pour ça, la mémoire n'était pas assez lente. Le modèle TSO de l'époque n'autorisaient que peu de lectures anticipées. Une lecture ne peut être exécutée en avance que sous les conditions suivantes :
* les écritures contournées se font dans la mémoire cache ;
* la lecture doit se faire dans la mémoire RAM ;
* les écritures se font à une adresse différente de la lecture ;
* aucune transaction avec un périphérique ne doit être en cours.
A partir du Pentium 4, les choses changent. Le Pentium 4 est en effet le premier processeur à implémenter des techniques permettant d’exécuter plusieurs processus en parallèle, dont l'Hyperthreading. En conséquence, le modèle de consistance a dû être assoupli pour éviter de perdre bêtement en performance. Il s'agit d'un vrai modèle de type TSO, la majeure partie des contraintes sur les lectures anticipées sont réduites au minimum. Il y a aussi des contraintes supplémentaires pour les instructions dites atomiques, ainsi que pour les instructions qui accèdent aux périphériques ou aux entrées-sorties. Une optimisation séparée du TSO est que les écritures en mémoire peuvent changer d'ordre dans un cas exceptionnel : les écritures effectuées par les instructions de copie mémoire comme REPMOVSD, REPSCACB, ainsi que les instructions SSE MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, MOVNTPD. Les écritures effectuées dans ces instructions peuvent se faire dans un désordre complet (ou presque).
Lors du passage au 64 bits, le modèle mémoire des processeurs x86 de l'époque a été formalisé dans un papier d'Intel appelé [https://www.cs.cmu.edu/~410-f10/doc/Intel_Reordering_318147.pdf Intel® 64 Architecture Memory Ordering White Paper], mis à jour en 2008 suite à quelques problèmes techniques. La formalisation du modèle a été d'une grande aide aux programmeurs, qui devaient se débrouiller avec des documentations qui ne formalisaient pas de modèle de consistance précis. Le document décrit un modèle de consistance mémoire portant le nom barbare de ''total lock order + causal consistency” (TLO+CC)'', qui admet plus d'optimisations que le modèle TSO. Mais la description était en réalité légèrement différente du modèle mémoire réellement implémenté sur les processeurs existants, qui utilisaient du TSO, sauf en de rares occasions.
Les concepteurs de processeur font souvent cela, à savoir définir un modèle de consistance plus laxiste que réellement implémenté, pour des questions de comptabilité. Ainsi, si les futurs processeurs de la marque implémentent un modèle plus laxiste, les programmes déjà codés continuent de fonctionner. Le modèle décrit dans la documentation laisse de la marge pour le futur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le scoreboarding et l'algorithme de Tomasulo
| prevText=Le scoreboarding et l'algorithme de Tomasulo
| next=Le parallélisme mémoire
| nextText=Le parallélisme mémoire
}}
</noinclude>
mcyv7dkf62xbkwdby4qmv70t6gfatn2
773412
773411
2026-09-28T00:40:13Z
Mewtow
31375
/* La Load/Store Queue */
773412
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons vu la mal-nommée exécution dans le désordre dans les chapitres précédents, qui devrait plutôt s'appeler l'émission dans le désordre. Elle modifie l'ordre des instructions pour gagner en efficacité, mais cela n'est valable que pour les instructions travaillant sur des registres ! Nous n'avons pas parlé de l'exécution dans le désordre des accès mémoire, car ils sont un peu à part. Et pour comprendre pourquoi, nous allons dédier ce chapitre à la gestion des accès mémoire dans le pipeline.
==L’émission des accès mémoire==
Avant de poursuivre, faisons un rappel rapide. Il faut distinguer deux types de dépendances de données : les dépendances de registres (deux instructions manipulent le même registre), alors que l'unité mémoire gère les dépendances d'adresse (deux instructions manipulent la même adresse). La différence registre-adresse fait que la gestion des dépendances est totalement différente.
===L'''aliasing'' mémoire et les dépendances mémoire===
Pour rappel, deux instructions ont une dépendance d'adresse si elles écrivent ou lisent la même adresse mémoire, ce qui interdit de changer leur ordre. Des instructions mémoires qui lisent/écrivent des adresses différentes sont indépendantes. Lorsque deux instructions mémoire lisent/écrivent à la même adresse, on parle d''''aliasing mémoire'''. Détecter les cas d'aliasing mémoire est primordial pour gérer la désambiguïsation mémoire, et cela demande de comparer des adresses.
Formellement, l'aliasing mémoire n'est ni plus moins que l'application des dépendances de données aux instructions mémoire. Et comme les autres dépendances de données, il existe plusieurs types d'aliasing mémoire : RAR, RAW, WAR et WAW. Les dépendances RAR ne posent aucun problème, les lectures peuvent changer d'ordre sans aucun problème, tant qu'il n'y a pas d'écritures entre les deux. Par contre, les autres dépendances imposent trois contraintes.
* Les dépendances RAW imposent que les lectures ne doivent pas s'exécuter avant une écriture à la même adresse.
* Les dépendances WAR imposent que l'on ne peut pas déplacer une écriture avant une lecture à la même adresse.
* Enfin, les dépendances WAW font que l'ordre des écritures doit être celui du programme : impossible de changer l'ordre de deux écritures.
Les trois contraintes peuvent s'implémenter de plusieurs manières différentes. Comme pour les dépendances de données normales, seules les dépendances RAW sont de vraies dépendances de données. Les deux autres dépendances viennent du fait que l'on utilise la même adresse pour stocker des valeurs différentes. S'il existe des techniques similaires au renommage de registre permettent de les faire disparaitre, elles sont très limitées.
===La désambiguïsation mémoire===
Diverses optimisations permettent d'émettre des accès mémoire dans le désordre, exactement comme l’exécution dans le désordre le fait pour les autres instructions. Elles utilisent pour cela des fenêtres d'instruction spécialisées dans les accès mémoire. Il faut vraiment insister sur le fait que l'exécution dans le désordre proprement dite ne se préoccupe pas des micro-opérations mémoire. Pour faire la distinction, on parle de '''désambiguïsation mémoire''' (''memory disambiguation'') pour parler de l'exécution dans le désordre des accès mémoire.
Le but de la désambiguïsation mémoire est d'exécuter les lectures le plus tôt possible. La raison est que la donnée lue est utilisée par d'autres instructions, dépendantes de la lecture. Elles sont donc à la tête d'une chaine de dépendances de données qui peut bloquer le pipeline si elle n'est pas résolue très tôt. Dès qu'une lecture peut être lancée, elle accède directement au cache de données. Par contre, si l'aliasing ne le permet pas (écriture à la même adresse pas encore effectuée) ou que l'adresse à lire n'a pas encore été calculée, la lecture attend son tour.
Le problème est que la désambiguïsation mémoire demande de comparer des adresses, pour détecter les dépendances d'adresse. Et cela pose de sérieuses contraintes pour son implémentation. En comparaison, l'exécution dans le désordre normale compare des registres, ce qui est beaucoup plus simple. Les registres utilisés par une instruction sont directement encodés dans l'instruction elle-même, ce qui fait qu'on peut détecter les dépendances de registre à l'émission, voire au décodage. Et elles peuvent même être éliminées par le renommage de registre pour les dépendances WAW et WAR. Par contre, les adresses sont des opérandes d'instruction, elles ne sont pas encodées dans l'instruction elle-même. Il y a certes d'exception de l'adressage absolu, mais elle est minoritaire. Avec l'adressage indirect ou indicé, les adresses sont soit lues depuis les registres, soit calculées par une unité de calcul. Les adresses ne sont donc connues qu'une fois que l'instruction a attendu suffisamment de temps dans la fenêtre d'instruction.
===Le calcul anticipé des adresses mémoire===
La désambiguïsation mémoire fonctionne d'autant mieux que les adresses à lire/écrire sont connues le plus tôt possible. Pour cela, il est possible de séparer les accès à la mémoire en deux micro-instructions : une pour le calcul d'adresse et l'autre pour les accès mémoire. Cela permet de calculer les adresses dès que possibles, et donc de vérifier à l'avance si l'adresse calculée a des dépendances avec celles des autres instructions. La détection des dépendances est ainsi anticipée de quelques cycles, permettant un accès anticipé sûr des accès mémoire. On parle de '''calcul anticipé des dépendances'''.
Une implémentation possible effectue les calculs d'adresse dans les unités de calcul entière. L'ALU entière et l'unité mémoire sont alors dans deux avals séparés, en parallèle. La micro-opération de calcul d'adresse s'exécute dans une ALU, et son résultat est envoyé à l'unité mémoire. Pour cela, l'adresse calculée est envoyée à l'unité mémoire via un système de contournement dédié, qui relie les sorties des ALU entières à l'unité mémoire.
Une solution alternative utilise un aval unique qui s'occupe à la fois des micro-opérations entières et des micro-opérations mémoire. L'aval a alors deux étages en série, un pour l'unité de calcul, suivi par l'étage d'accès mémoire. Le pipeline RISC classique était dans ce cas, pour rappel. Cette solution permet d'avoir un pipeline dit de longueur fixe, à savoir que toutes les instructions font le même nombre de cycles d'horloge. De plus, elle garantit que les instructions mémoire sont décodées en une seule micro-opération. En clair, il y a un seul aval pour toutes les instructions. Mais elle vient avec un défaut majeur : de nombreuses instructions n'utilisent pas l'unité mémoire, mais doivent quand même traverser l'étage associé, qui passe un cycle à ne rien faire.
==L’émission dans l'ordre des accès mémoire==
Avant toute chose, faisons quelques rappels sur l'exécution dans le désordre. Les micro-opérations renommées sont mises en attente avant que les conditions pour leur émission soient remplies. La mise en attente peut se faire soit dans une file de micro-opération, soit dans une fenêtre d'instruction. Une file de micro-opération est une mémoire FIFO qui garde les micro-opération dans leur ordre d'émission, l'ordre du programme. Les micro-opérations sont donc émises dans l'ordre du programme, il n'y a pas d'émission dans le désordre. Par contre, les fenêtres d'instructions peuvent émettre les micro-opérations dans le désordre, dans un ordre différent de celui du programme. Dans les deux cas, les écritures sont retardées via une file d'écriture, qu'on a vu il y a quelques chapitres et sur laquelle on fera des rappels.
===La file de µops mémoire===
Avec l''''émission dans l'ordre des accès mémoire''', le processeur émet les accès mémoire dans l'ordre du programme. Cela implique que l'unité mémoire n'a pas de fenêtre d'instruction, ni de station de réservation associée. À la place, les micro-opérations mémoire sont placées dans une file de micro-opération dédiée, appelée la '''file de µops mémoire'''. Par contre, pour les unités de calcul, le processeur utilise des fenêtres d'instruction. Autant les accès mémoire se font dans l'ordre, autant les instructions arithmétiques/logiques/branchements/autres sont exécutées dans le désordre. En clair, le processeur peut supporter l'exécution dans le désordre, sauf pour les micro-opérations mémoire.
[[File:Processeur avec émission dans l'ordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans l'ordre des accès mémoire]]
: Nous avons volontairement omis le cas où le processeur n'a pas d'exécution dans le désordre, où il y a une file de micro-opération unique. Mais c'est globalement la même chose, car une file de micro-opération unifiée émet toutes les instructions dans l'ordre.
La file de µops mémoire est une mémoire FIFO, qui contient plusieurs entrées, chaque entrée pouvant accueillir une µops mémoire. Une entrée contient plusieurs champs : un qui précise si l'opération est une lecture ou une écriture, un pour l'adresse mémoire à lire/écrire, un autre pour la donnée à écrire (qui est vide pour une lecture), un autre pour le registre de destination des lectures. Les deux derniers champs sont souvent fusionnés en un seul, qui est interprété différemment selon que l'instruction est une lecture ou une écriture. Elle contient aussi des bits de validité, qui indique si l'adresse ou la donnée sont disponibles, ou si le champ associé est vide.
[[File:Load Store Queue unifiée.png|centre|vignette|upright=2.5|File de µops mémoire.]]
L'unité mémoire ne permet que de faire un accès mémoire à la fois. Du moins, elle a un port sur lequel on envoie la micro-opération émise, que ce soit une écriture ou une lecture. Vu de l'extérieur, il faut attendre qu'un accès mémoire soit terminé pour lancer le suivant. Avant d'émettre une micro-opération mémoire, la file de micro-opération (son ''scoreboard'') vérifie si l'unité d'accès mémoire est libre ou non. Si elle est occupée, un accès mémoire est en cours, et on ne peut pas émettre de micro-opération mémoire. Une fois l'accès mémoire terminé, la mémoire ou le cache envoient un signal pour indiquer que l'accès est fini, et c'est au tour d'une nouvelle micro-opération mémoire.
[[File:Unité de gestion des accès mémoires dans l’ordre.png|centre|vignette|upright=2.0|Unité de gestion des accès mémoires dans l’ordre]]
Mais il s'agit là du comportement vu de l'extérieur de l'unité mémoire. En réalité, l'unité mémoire utilise quelques optimisations en interne qui permettent d'exécuter dans le désordre les micro-opérations mémoire.
===La file d'écriture : une FIFO de mise en attente des écritures===
Afin de supporter les exceptions précises, les écritures doivent respecter deux contraintes : s'exécuter dans le tout dernier étage du pipeline, s'exécuter dans l'ordre du programme. Pour cela, les écritures sont mises en attente tant que les instructions précédentes ne sont pas terminées. Les écritures dans le cache sont mises en attente dans une mémoire FIFO, appelée la '''file d'écriture''', ou encore la ''Write Queue''. Nous en avions déjà parlé dans le chapitre sur le pipeline, dans la section sur les exceptions précises. Mais quelques rappels ne feront pas de mal.
L'écriture est insérée dans la file d'écriture lorsqu'elle est émise, elle en sort à l'étage de ''Writeback'' si tout se passe bien. Si une exception est détectée, l'étage de ''Writeback'' vide la file d'écriture, ce qui annule les écritures mises en attente, faites à tort. De plus, la file d'écriture est une mémoire FIFO, qui mémorise les écritures dans l'ordre du programme. En effet, les écritures sont ajoutées dans la file d'écriture lorsqu'elles sont émises, et elles sont émises dans l'ordre du programme.
[[File:Store Queue sur un pipeline fixe.png|centre|vignette|upright=2|Write Queue sur un pipeline fixe]]
La file d'écriture garantit que les écritures sont dans l'ordre du programme. Et cette propriété élimine totalement les dépendances WAW. Le fait que les écritures se font à la fin du pipeline élimine quant à elle les dépendances WAR. Il ne reste plus qu'à gérer les dépendances RAW, celles où une lecture lit une donnée écrite par une autre instruction. Une telle situation survient, car les écritures sont mises en attente pendant un certain temps, la donnée écrite peut être lue pendant ce laps de temps. La lecture ne peut pas lire la donnée dans le cache, car la donnée à lire n'y est pas encore enregistrée. Le cas est rare, car il demande de lire une donnée qu'on vient d'écrire, mais il existe. Il se manifeste surtout sur les processeurs avec peu de registre, où des données doivent régulièrement être déplacées temporairement en mémoire RAM.
Pour éviter cela, une solution consiste mettre en attente les lectures si une écriture est en attente. Les lectures sont maintenues dans la file de µops tant que la file d'écriture n'est pas vide. La file de µops mémoire est suivie d'un circuit de détection des dépendances, un '''''scoreboard'' mémoire''' qui est assez simple. L'unité mémoire fournit un bit qui indique si la file d'écriture est vide ou non, ainsi qu'un second bit qui indique qu'elle est vide. Les deux ne sont pas compliqués à générer, ils font partie intégrante de l'interface de toute mémoire FIFO digne de ce nom. Le ''scoreboard'' mémoire utilise ces deux bits pour savoir s'il peut émettre une micro-opération mémoire. Il n'émet pas de lecture tant que la file d'écriture n'est pas vide. Il peut par contre émettre des écritures tant qu'elle n'est pas pleine.
Une meilleure solution serait d'interdire les lectures, mais seulement en cas de dépendance mémoire détectée. Et c'est là qu'intervient la technique du '''''load bypassing'''''. Elle autorise une lecture, si elle n'a aucune dépendance avec les écritures mises en attente dans la file d'écriture. Elle doit être mise en attente dans le cas contraire. Pour l'implémenter, il faut comparer l'adresse à lire avec toutes les adresses dans la file d'écriture. La lecture est exécutée immédiatement si aucune correspondance n'est trouvée, mais elle est mise en attente dans le cas contraire. Pour cela, la file d'écriture est modifiée et devient un mélange entre FIFO et mémoire associative. L'adresse à lire est envoyée à la file d'écriture, et celle-ci vérifie si une écriture est en attente à cette adresse. Si c'est le cas, c'est un succès de ''Write Queue'' : la lecture doit être bloquée. Sinon, c'est un défaut de ''Write Queue'', la lecture accède au cache, là où se trouve la donnée à lire.
Dans le chapitre sur le contournement, nous avons parlé d'une forme de contournement qui corrige ce problème. Les lectures peuvent lire la donnée depuis la file d'écriture si besoin, ce qui renvoie la donnée adéquate. Pour cela, les lectures consultent à la fois le cache et la file d'écriture, comme dit plus haut. L'adresse à lire est envoyée à la file d'écriture, et celle-ci vérifie si une écriture est en attente à cette adresse. Si c'est le cas, c'est un succès de ''Write Queue'', et celle-ci envoie alors la donnée associée à l'étage d'accès mémoire. Sinon, la lecture accède au cache, là où se trouve la donnée à lire. La solution porte le nom de ''Store to load Forwarding'', ou encore de '''réacheminement écriture-vers-lecture'''.
[[File:Store to load forwarding sur un pipeline simple.png|centre|vignette|upright=2|Store to load forwarding sur un pipeline simple]]
La lecture est mise en attente dans l'unité mémoire, vu que c'est elle qui fait les comparaisons d'adresse. Du point de vue extérieur, le ''scoreboard'' mémoire ne voit pas s’il y a eu succès dans la file d'écriture ou non. Il voit juste que l'unité mémoire est occupée et ne peut pas accepter de nouvelle lecture. Elle reste occupée durant quelques cycles, en cas de défaut de ''Write Queue'', le temps de faire la lecture dans le cache. Par contre, en cas de succès de ''Write Queue'', elle reste occupée tant que la lecture n'a pas donné de résultat, ce qui inclue le temps de mise en attente.
Les processeurs haute performance implémentent tous le ''Store to load Forwarding'', mais quelques processeurs basse performance ne le font pas. Ils se contentent du ''load bypassing'', qui offre des performances correctes pour un cout en matériel bien plus modeste. Pour donner un exemple, la micro-architecture Jaguar d'AMD était dans ce cas. Elle a été utilisée sur les consoles de jeu de 8ème génération, comme la Xbox One et la Playstation 4.
Les processeurs haute performance préférent utiliser plus de circuits pour implémenter le ''Store to load Forwarding'', mais l'implémentation n'est souvent pas complète. En effet, l'implémentation est souvent simple et ne prend en compte que des lectures/écritures à des adresses identiques, sans compter que la taille des données doit aussi être identique. Une écriture d'un entier de 32 bit sera contournée et envoyée à une lecture de 32 bits à la même adresse. Mais la lecture lit seulement 16 bits sur les 32 écrits, le contournement peut ne pas marcher. Concrètement, ce cas précis fonctionnera. Mais le cas inverse, avec une lecture lisant plusieurs écritures de taille plus petite, ne marchera pas sur tous les processeurs. Ne parlons pas du cas où les données écrites sont à cheval sur deux lignes de cache.
===Le contournement des adresses mémoire avec une file de µops===
Il faut noter qu'au moment où les instructions LOAD et STORE sont émises, leur adresse n'est pas forcément connue. Sauf en cas d'adressage absolu, mais mettons ce cas particulier de côté, il est assez rare en pratique. Les micro-opérations mémoire sont donc insérées dans la file de µops, mais l'adresse est souvent disponible plus tard. Il faut donc modifier le chemin de données pour que l'adresse soit envoyée à la file de µops mémoire.
Pour simplifier, étudions le cas d'un processeur où les instructions LOAD/STORE ne gèrent que l'adressage indirect à registre, à savoir que l'adresse est lue dans un registre. Les calculs d'adresse sont réalisés par une instruction de calcul séparée de l'instruction LOAD/STORE. Tout calcul d'adresse a lieu dans les unités de calcul entière, les micro-opérations mémoire ne font pas de calcul d'adresse.
Au minimum, il doit y avoir une connexion entre le banc de registre et la file de µops mémoire. Il faut alors attendre que les adresses soient enregistrées dans les registres pour être envoyées à la file de µops mémoire. Cependant, il est possible de profiter du réseau de contournement. L'idée est que l'adresse est accessible avant via le réseau de contournement, avant même d'être enregistrée dans les registres L'idée est que l'adresse est envoyée à l'unité mémoire dès qu'elle sort des unités de calcul. Le gain en performance étant important, tous les processeurs modernes ajoutent un système de contournement qui envoie les adresses à la file de µops mémoire. L'ALU entière calcule l'adresse finale, l'envoie à la file de µops mémoire.
[[File:Contournement pour la file de µops mémoire.png|centre|vignette|upright=2|Contournement pour la file de µops mémoire]]
Maintenant, les instructions LOAD/STORE avec un mode d'adressage indicé, ce qui signifie que l'instruction fait un calcul d'adresse et l'accès mémoire proprement dit. Une solution basique utilise une micro-opération séparée pour le calcul d'adresse et l'accès mémoire. Le calcul d'adresse envoie son résultat à la file de µops mémoire, sans l'enregistrer dans les registres, via contournement. Sans optimisation particulière, les calculs d'adresse sont réalisés par les ALU entières, le processeur n'est pas modifié et reste le même que celui du schéma précédent.
Mais une autre solution utilise des ALU spécialisées dans les calculs d'adresse, appelées des ''Adress Generation Unit'', abrévié AGU. L'avantage est que cela simplifie grandement le réseau de contournement : seules les AGU sont connectées à la file de µops mémoire. Un autre avantage est lié à l'adressage de la destination. En utilisant des opérations entières usuelles, il faudrait préciser que le résultat est à destination de la file de µops mémoire, pour configurer le réseau de contournement. Avec des AGU, les calculs d'adresse sont des micro-opérations différentes des additions et soustraction, elles sont envoyées au AGU spécifiquement, pas besoin de préciser la destination.
[[File:File de µops mémoire avec calcul d'adresse via AGU.png|centre|vignette|upright=2|File de µops mémoire avec calcul d'adresse via AGU]]
Lors de l'émission, la micro-opération mémoire est insérée dans la file de µops mémoire, l'opération de calcul d'adresse est émise dans la fenêtre d'instruction. Et les deux sont exécutés différemment. En effet, un calcul d'adresse est une opération arithmétique, qui est gérée par le système d'exécution dans le désordre. Elle peut s'exécuter dès que ses opérandes sont disponibles. À l'inverse de l'accès mémoire, qui lui doit se faire dans l'ordre du programme. Les adresses sont donc calculées en avance, puis insérées dans la file de µops mémoire. La µops de calcul d'adresse précise où enregistrer son résultat, le résultat est enregistré dans la bonne entrée de la file de µops mémoire.
Les techniques précédentes décodent une instruction LOAD/STORE en deux micro-opérations. Mais il est possible de la décoder en une seule. Le calcul d'adresse est alors réalisé dans l'unité mémoire. Les opérandes du calcul d'adresse sont lues depuis les registres lorsque l'instruction quitte la file de µops mémoire, du moins pour les opérandes qui n'ont pas été contournées. Là, elles sont envoyées à l'unité mémoire, qui fait le calcul d'adresse. Dans les faits, aucun processeur commercial moderne n'utilise cette technique.
[[File:File de µops mémoire avec calcul d'adresse dans l'unité mémoire.png|centre|vignette|upright=2|File de µops mémoire avec calcul d'adresse dans l'unité mémoire]]
===L'optimisation du pseudo-contournement des opérandes mémoire===
Les processeurs modernes implémentent une optimisation concernant les accès mémoire. Cette optimisation a été décrite par Agner Fog dans ses manuels d'optimisation, sous le terme de '''''Mirroring memory operand'''''. Il a documenté son apparition sur les processeurs Zen 2 d'AMD, puis son évolution sur les processeurs Zen 4 et 5. L'optimisation est bizarrement absente de la microarchitecture Zen 3.
Elle se manifeste dans une condition très précise : plusieurs instructions proches accèdent à la même adresse. Les deux instructions ne sont pas forcément consécutives, il peut y avoir une dizaine ou centaine d'instructions entre les deux. Une telle situation survient souvent sur les CPU CISC, et notamment sur les CPU x86, car ils ont peu de registres. les compilateurs font donc beaucoup d'échanges entre pile d'appel et registres, ce qui fait que des données écrites dans la pile sont souvent relue quelques instructions plus tard.
Dans ce cas, la donnée lue/écrite est mémorisée dans un registre interne au processeur et peut être accédée très rapidement. Par exemple, imaginons plusieurs lectures proches à la même adresse, sans écriture entre elles. Dans ce cas, le processeur ne fera qu'une seule lecture en mémoire RAM, les lectures ultérieures liront la donnée depuis le registre interne. Comme autre exemple, imaginons une écriture suivie plus tard par une lecture à la même adresse. Dans ce cas, la donnée sera écrite dans un registre interne, en plus d'être propagée dans le cache ou la RAM, et la lecture ultérieure lira directement le registre interne. Le gain en performance est alors conséquent, comparé à du simple ''store-to-load forwarding''.
Il faut noter que la technique marche aussi pour les opérations ''load-op'', pas seulement pour les instructions d'accès mémoire. Par exemple, prenons une écriture en RAM, suivies quelques instructions plus tard par une opération ''load-op'' à la même adresse. L'écriture écrit à une adresse, qui est lue par l'instruction ''load-op''. L'optimisation s'applique : l'instruction ''load-op'' lira l'opérande mémoire depuis un registre interne du processeur, qui contient une copie de la donnée écrite. Sur les processeurs Zen 2, l'optimisation marchait aussi pour les instructions PSUH et POP, qui lisaient/écrivaient dans la pile d'appel. Mais cette possibilité a été retirée sur les processeurs Zen 5.
La technique détecte que deux instructions accèdent à la même adresse sous certaines conditions bien précises. L'optimisation ne marche que pour les modes d'adressages indicés et indirect, avec ou sans décalages, mais pas en mode absolu ou relatifs. Il faut préciser que l'optimisation ne marche que pour les registres généraux, pas les registres flottants. Les registres doivent être précisés de la même manière dans toutes les instructions. Par exemple, le processeur ne reconnait pas que l'addition des deux registres RCX + RAX est la même que RAX + RCX. Sur les CPU Zen 2 et 4, elle ne marchait que pour des opérandes de 32 et 64 bits, mais les processeurs Zen 5 gèrent des données de 8 et 16 bits aussi. Les décalages étaient limités à 8 bits sur le Zen 2, mais n'a plus de limites sur les Zen 4 et 5.
==Le ''Load Ordering, Store Ordering''==
Intuitivement, on dit qu'exécuter des µops mémoire dans le désordre demande de remplacer la file de µops mémoire par une fenêtre d'instruction. Et si c'est en effet une solution intéressante, nous n'allons pas la voir toute de suite. Nous allons étudier le cas où cette file deµops mémoire est scindée en deux : une file pour les lectures et une autre pour les écritures.
===Les files de µops LOAD et STORE===
Dans le chapitre sur l'exécution dans le désordre, nous avons vu deux possibilités pour implémenter l'exécution dans le désordre. La première utilise une fenêtre d'instruction ou une station de réservation, l'autre utilise plusieurs files de µops séparées. Et cette technique peut parfaitement s'utiliser pour les micro-opérations mémoire. Les micro-opérations mémoire sont réparties dans deux files de µops mémoire séparées, chacun émettant des micro-opérations indépendamment de l'autre. Mais comment répartir les micro-opérations mémoire dans deux files, simplement et sans que cela pose des problèmes de dépendances de données ? La réponse à ces contraintes est toute simple : on utilise une file de µops spécialisée pour les lectures, et une autre pour les écritures.
Les cours d'architecture des ordinateurs nomment souvent ces deux files en utilisant les termes de ''file de STORE'' (''store queue'') et de ''file de LOAD'' (''load queue''). Dans ce qui suit, nous parlerons de '''file de µops LOAD''' et de '''file de µops STORE''', pour bien préciser qu'il s'agit de file de µops. La file de STORE ne doit pas être confondue avec la file d'écriture, qui est appelée ''write queue'' en anglais. Les termes ''store queue'' et ''write queue'' désignent deux choses différentes, mais il y a un risque de confusion pour qui n'est pas assez attentif. D'autant plus que la méthode que nous allons voir a une file d'écriture en plus des deux files de µops mémoire.
En effet, la file d'écriture ne mémorise pas des micro-opérations, juste des écritures déjà émises et en attente. Une entrée dans la file d'écriture contient une adresse et la donnée à écrire. Alors que dans la file de µops STORE, il se peut que l'adresse ou la donnée soient manquantes, car pas encore disponibles. De plus, la file d'écriture met en attente des écritures tant que les instructions précédentes ne sont pas terminées, alors que la file de µops STORE met en attente les micro-opérations avant leur émission. La première attend le feu vert de l'étage de ''Writeback'', l'autre attend le feu vert de l'étage de ''schedule'', d'un ''scoreboard'' amélioré.
Les deux files sont chacune connectées à l'unité mémoire, via un port dédié. L'unité mémoire a donc un port pour les lectures, et un port pour les écritures. Le port d'écriture est relié à la file d'écriture, le port de lecture est directement relié au cache de données. il faut noter qu'une unité mémoire à deux ports n'est pas si bizarre que cela. En effet, le cache de données a lui aussi deux ports, dans la majorité des cas, avec un port de lecture et un port d'écriture.
[[File:Load Ordering, Store Ordering.jpg|centre|vignette|upright=2|Exécution dans le désordre des lectures.]]
Le fait de séparer lectures et écriture a de nombreux avantages. Le premier est qu'une file de µops mémoire unique gâche des transistors. Une écriture a besoin de mémoriser une adresse et une donnée, avec les bits de validité pour les deux. Une lecture n'a besoin que d'une adresse et du bit de validité associé. Avec une file de µops unique, chaque entrée de la file est suffisamment large pour contenir une écriture. Si elle ne contient qu'une lecture, elle est trop large, ce qui sous-utilise les circuits de la file. Avec des files de lecture/écriture séparées, on n'a pas ce problème, chaque file contient juste ce qu'il faut. Il y a donc une économie de transistors.
Cependant, le système est moins flexible. Par exemple, prenons une file de µops mémoire unique, de 16 entrées. Elle peut mémoriser indifféremment 16 lectures, 16 écritures, ou n'importe quel mix d'écriture et de lectures tant qu'il se limite à 16 µops. Avec des files séparées, ce n'est plus le cas. Prenons par exemple le cas avec une file de µops LOAD de 10 éléments et une file de µops STORE de même capacité : impossible d'avoir 16 écritures en attente. Cependant, ce désavantage est quelque peu compensé par l'économie de transistor mentionnée précédemment, qui est réinvestie pour agrandir les files de µops. Typiquement, on agrandit la file de µops LOAD, car les lectures sont plus fréquentes que les écritures dans la majorité des programmes. En ayant une file de µops LOAD assez conséquente, le désavantage est fortement atténué, voire disparait.
===La gestion des dépendances avec des files de µops mémoire séparées===
L'usage de ces deux files de µops mémoire porte le nom de '''''Load Ordering, Store Ordering'''''. Il est intéressant de comparer cette technique avec la précédente, qui utilisait une file de µops mémoire séparée. Avec deux files séparées, les lectures s'exécutent dans l'ordre, les écritures s'exécutent dans l'ordre, mais les lectures peuvent passer avant/après les écritures et réciproquement, tant que l'aliasing le permet. Les écritures peuvent prendre de l'avance sur les lectures, ou réciproquement. Et cette avance est très utile si elle permet d'exécuter des lectures en avance. En comparaison, avec une file de µops unique, ni écritures ni lectures ne peuvent prendre de l'avance. Les performances sont donc améliorées, bien que de peu.
Néanmoins, l'avance des lectures est limitée. Prenons le cas où une lecture doit être mise en attente, car son adresse n'est pas disponible. Avec une file de µops mémoire unique, la lecture bloque toutes les micro-opérations qui suivent. Avec deux files séparées, elle bloque uniquement les lectures, pas les écritures. Les écritures qui suivent la lecture peuvent s'exécuter. Le cas avec une écriture en attente est un peu différent. Une écriture en attente bloquera les écritures suivantes. Mais elle bloquera aussi les lectures postérieures : du point de vue de ces lectures, une écriture précédente a une adresse inconnue, on ne sait pas s'il y a dépendance mémoire avec et on suppose que oui dans le doute. En clair, l'avance des lectures n'a lieu que si les adresses des écritures précédentes sont connues, déjà calculées, sans quoi on ne peut pas détecter les dépendances mémoire.
Le fait que les écritures peuvent être émises en avance fait que la détection des dépendances RAW est différent. L'idée générale reste cependant la même. Lors de l'émission, une lecture doit vérifier ses dépendances avec les écritures antérieures, une écriture doit vérifier ses dépendances avec les lectures ultérieures. Avec une file de µops unique, on est certain que les écritures antérieures sont dans la file d'écriture, dans l'unité mémoire. Et lorsqu'on émet une écriture, on est certain que les lectures ultérieures seront émises après. Avec deux files séparées, ce n'est pas forcément le cas. Si les lectures ont pris de l'avance, une écriture précédente peut être en attente dans la file de µops STORE, alors qu'une lecture ultérieure a été émise. Ce qui complique la détection des dépendances RAW d'adresse. Les détecter demande non seulement de vérifier la file d'écriture, mais aussi la file de µops STORE.
Dans ce qui suit, on suppose que l'unité mémoire incorpore une file d'écriture, ce qui est toujours le cas en pratique. Le fait que les écritures se font dans l'ordre élimine les dépendances WAW et WAR. On suppose aussi que l'unité mémoire implémente le ''réacheminement écriture-vers-lecture'' (''store-to-load forwarding''). Ainsi, si une lecture est émise dans l'unité mémoire, les dépendances RAW avec les écritures déjà émises ne sont pas un problème. Par contre, il faut gérer les dépendances avec les écritures en attente dans la file de µops STORE, dont certaines sont antérieures à la lecture dans l'ordre du programme. Pour cela, il n'y a pas le choix : pour chaque lecture, on vérifie dans la file de µops STORE si elle a une dépendance avec une écriture antérieure. La gestion des dépendances RAW est donc gérée partiellement dans l'unité mémoire et la file de µops STORE.
Pour l'implémenter, il faut comparer l'adresse à lire avec toutes les adresses dans la file de µops STORE. En cas de ''match'', la lecture est bloquée dans la file de lecture. Mais s'il n'y a aucune correspondance, alors la lecture peut s'exécuter. Pour cela, la file de µops STORE est un mélange entre FIFO et mémoire associative. Il faut préciser que la comparaison ne prend en compte que les écritures situées avant la lecture, dans l'ordre du programme. Les deux files de µops doivent donc mémoriser des informations sur l'ordre d'émission. De plus, il arrive que des écritures soient dans la file d'écriture, mais que leur adresse n'ait pas encore été calculée. Dans ce cas, il y a potentiellement dépendance avec la lecture si l'adresse se révèle être la même une fois calculée : on considère que les adresses inconnues sont des correspondances/''match''.
La file de lecture met des lectures en attente, soit car elle attend ses opérandes, soit car elle attend que les lectures précédentes soient terminées, soit car l'aliasing ne lui permet pas. Elle a cependant un défaut : celui de forcer les lectures à s’exécuter dans l'ordre, ce qui est une contrainte inutile. Mais l'avantage est que l'implémentation est très simple. Une file de micro-opération est bien plus simple à implémenter qu'une fenêtre d'instruction.
==Le ''Partial Ordering'' : la ''Load/Store Queue''==
Avec la technique précédente, les lectures s'exécutent dans l'ordre, à savoir que l'on ne peut pas déplacer une lecture avant ou après une autre. Et cette limitation a des conséquences sur la performance de l'exécution dans le désordre. Des lectures sont mises en attente, parce qu'une lecture précédente l'est, alors qu'elles auraient pu s’exécuter avant. Et ce retard sur les lectures se répercute sur les instructions lecture-dépendantes, donc sur le reste du pipeline. Pour éviter cela, la technique du ''Partial Ordering'' permet aux lectures de s'exécuter dans le désordre tant que les conditions d'aliasing sont respectées.
===La ''Load/Store Queue''===
La technique s'implémente en remplaçant les files de µops mémoire par une fenêtre d'instruction spécialisée dans les µops mémoire. Elle est souvent appelée la '''''Load/Store Queue''''', qui est concrètement une station de réservation spécialisée dans les micro-opérations mémoire. En tout cas, nous utiliserons beaucoup l'abréviation LSQ pour parler de la ''Load/Store Queue''.
[[File:Processeur avec émission dans le désordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans le désordre des accès mémoire]]
Une entrée de la ''Load/Store Queue'' contient plusieurs champs : un qui précise si l'opération est une lecture ou une écriture, un pour l'adresse mémoire à lire/écrire, un autre pour la donnée à écrire (qui est vide pour une lecture), un autre pour le registre de destination des lectures (inutile pour les écritures). Les deux derniers champs sont souvent fusionnés en un seul, qui est interprété différemment selon que l'instruction est une lecture ou une écriture.
[[File:Load Store Queue unifiée.png|centre|vignette|upright=2.5|''Load/Store Queue''.]]
Lorsqu'une micro-opération mémoire est émise, après renommage de registres, elle est insérée dans la ''Load/Store Queue''. Si l'adresse à lire/écrire est disponible, elle est immédiatement lue depuis les registres, idem pour une éventuelle donnée à écrire. Sinon, la micro-opération attend dans la LSQ que son adresse soit calculée par l'ALU entière. Pour gérer ce cas, elle contient des bits de validité pour chaque champ qui indiquent si l'adresse d'accès est connue, et si la donnée à écrire est disponible pour une écriture.
Il faut noter que tout ce qui a été dit plus haut à propos du contournement des adresses vaut aussi avec une ''Load/Store Queue'', à un détail près. Pour détecter les dépendances, la LSQ doit comparer les adresses à lire/écrire, ce qui fait qu'elles doivent avoir été calculées avant d'entrer dans la LSQ. En clair, il n'est pas possible de faire les calculs d'adresse dans l'unité mémoire, juste avant d’accéder au cache. Les unités de calcul d'adresse sont réalisés dans une ALU entière ou une AGU spécialisée.
Lorsqu'une instruction mémoire est émise, elle est décodée en deux micro-opérations : une pour le calcul d'adresse, une autre pour l'accès mémoire proprement dit. Le calcul d'adresse est réalisé soit dans une ALU entière, soit dans une AGU spécialisée. L'accès mémoire attend dans la ''Load/Store Queue'' que l'adresse soit calculée. Une fois l'adresse calculée, elle est envoyée à la ''Load/Store Queue'' et est enregistrée dans l'entrée adéquate. Reste qu'il faut envoyer l'adresse des ALU à la LSQ. Pour cela, on réutilise le réseau de contournement du processeur.
L'implémentation la plus simple effectue les calculs d'adresse dans les ALU entières. La sortie de toutes les ALU entière est reliée à la LSQ.
[[File:Load-Store Queue.png|centre|vignette|upright=2|Load-Store Queue]]
Les processeurs modernes préfèrent utiliser des unités de calcul d'adresse dédiées, afin de simplifier le réseau de contournement.
[[File:ALU et LSQ.png|centre|vignette|upright=1.5|ALU et LSQ]]
En pratique, les LSQ proprement dites sont peu efficaces, pour un budget en transistor donné. La raison est qu'une entrée de la LSQ doit mémoriser indifféremment lectures et écritures. Et elle contient pour cela plusieurs champs : l'adresse à lire/écrire, le registre pour la donnée à lire/écrire, et un champ pour la donnée à écrire. Or, pour une lecture, le dernier champ, pour la donnée à écrire, est inutile. Sachant que les lectures sont beaucoup plus nombreuses que les écritures, ce champ sera gaché pour la majorité des instructiosn, et les transistors associés le seront aussi. Une solution pour cela est d'utiliser une file séparée pour les lectures et écritures.
===La détection des dépendances mémoire===
La ''Load/Store Queue'' est couplée à un système de détection des dépendances mémoire, qui détermine quelles instructions peuvent s'exécuter sans problème. Et ce système de détection des dépendances est bien différent de celui d'une station de réservation normale. Les ''Load/Store Queue'' comparent non pas des noms de registres, mais des adresses/opérandes. Chaque micro-opération en attente compare son adresse avec celle de toutes les micro-opérations précédentes. Ou presque, la comparaison ne doit tenir compte que des écritures, et de celles situées avant dans l'ordre du programme. La ''Load/Store Queue'' doit donc mémoriser les instructions dans l'ordre du programme, tout en faisant des comparaisons d'adresse. Ça en fait une sorte hybride entre mémoire associative et mémoire FIFO.
Nous avions vu dans le chapitre "L'exécution dans le désordre" qu'il y a plusieurs manières pour implémenter une station de réservation : avec une mémoire associative, avec préplanification, avec des matrices de dépendances. Le problème est qu'on ne peut pas utiliser une mémoire associative pour la LSQ, qui utilise des comparateurs pour comparer les noms de registres/adresses. En effet, la détection des dépendances mémoire demande de faire beaucoup de comparaisons : chaque adresse est comparé à toutes les autres. Et les adresses faisant facilement 32 à 64 bits, les comparateurs sont bien plus imposants que ceux qui comparent des noms de registres de 5-10 bits. Le cout en matériel serait trop élevé, de même que le temps de calcul des dépendances. À la place, on utilise des matrices de dépendances.
La détection des dépendances mémoire utilise précisément deux matrices de ce type : une matrice de disponibilité, et une matrice de dépendance. Ces matrices forment une espèce de tableau carré, organisé en lignes et en colonnes. L'instruction dans la énième entrée se voit attribuer la énième ligne et la énième colonne. À l'intersection d'une ligne et d'une colonne, on trouve un bit qui indique si l'instruction de la ligne et celle de la colonne ont une dépendance.
La '''matrice de disponibilité''' permet de bloquer l'exécution des lectures/écritures tant qu'une instruction précédente n'a pas calculé son adresse, tant qu'on ne peut pas vérifier les dépendances. Lorsqu'une instruction est ajoutée dans le LSB, elle met tous les bits de la colonne attribuée à 1 si l'adresse à lire/écrire est inconnue, puis les met à 0 une fois l'adresse calculée. Une instruction ne peut pas démarrer si, sur la ligne qui lui est attribuée, se trouve un 1. Plus précisément, si on trouve un 1 pour les instructions plus anciennes, ce qui fait qu'une partie de la matrice est ignorée. Les bits notés X dans le tableau suivant sont ignorés, car ils précisent la disponibilité des opérandes d'un opérande ultérieure, ce qui est inutile. En clair, les calculs d'adresse modifient les colonnes, et on vérifie les lignes pour autoriser l'exécution.
{|class="wikitable"
|-
! Instructions mémoire dans la LSB,
triées de la plus ancienne à la plus jeune
! 0 !! 1 !! 2 !! 3 !! 4 !! 5 !! 6
|-
! 0
| 1 || X || X || X || X || X || X
|-
! 1
| 0 || 0 || X || X || X || X || X
|-
! 2
| 0 || 1 || 0 || X || X || X || X
|-
! 3
| 0 || 1 || 1 || 1 || X || X || X
|-
! 4
| 0 || 0 || 0 || 0 || 1 || X || X
|-
! 5
| 0 || 0 || 1 || 0 || 0 || 1 || X
|-
! 6
| 1 || 1 || 1 || 1 || 1 || 1 || 0
|}
Les '''matrices de dépendances''' fonctionnent sur le même principe, mais déterminent les dépendances mémoire. Lorsque le processeur a calculé une adresse, il la compare avec les adresses à lire/écrire déjà dans la LSB. Il met alors à jour les bits de la colonne de la matrice de dépendance : le bit à l'intersection d'une ligne et d'une colonne est mis à 0 si les deux instructions n'ont pas de dépendances, à 1 si elles en ont une. Une instruction mémoire est exécutée si tous les bits de sa ligne sont à 0. Là encore, on ne tient compte que des bits pour les instructions plus anciennes, la matrice a encore une fois une forme triangulaire, comme pour la matrice de disponibilité.
Une optimisation scinde la ''Load/Store Queue'' en deux structures, aux rôles légèrement différents. La première gère la disponibilité des opérandes, la seconde gère seulement les dépendances mémoire. La première est une station de réservation où les instructions sont mises en attente tant que leurs opérandes ne sont pas prêts. La seconde met en attente des micro-opérations dont les opérandes sont prêts, mais qui doivent attendre pour une autre raison : dépendance mémoire, cache occupé, etc.
L'avantage est qu'on remplace une grosse station de réservation par deux plus petites, mais l'intérêt est surtout relié au système de détection des dépendances. Sans cette optimisation, on a deux matrices énormes reliées à une ''Load/Store Queue'' unique. Avec la séparation, la matrice de disponibilité est reliée uniquement à la première station de réservation, alors que la matrice de dépendance est reliée uniquement à l'autre. Les deux matrices sont donc plus petites, car reliées à deux stations de réservation mémoire plus petites.
==L'exécution spéculative des accès mémoires==
Pour rappel, le but de la désambiguïsation mémoire est d'exécuter les lectures le plus tôt possible. Les données lues sont en effet, le départ d'une série d'instructions dépendantes, généralement des instructions arithmétiques, mais parfois d'autres lectures. Quand on manipule des structures de données, il n'est pas rare qu'une lecture lise un pointeur-adresse utilisé par une autre lecture.
Les techniques de désambiguïsation mémoire que nous avons vu précédemment sont dites conservatrices, à savoir qu'elles ne changent pas l'ordre de deux instructions mémoire si elles ont une dépendance d'adresse. Elles exécutent une lecture le plus tôt possible, mais seulement si on sait que celle-ci n'aliase pas d'écritures précédentes. Et cela demande de connaitre les adresses des écritures précédentes dans l'ordre du programme. Si une lecture est précédée par une ou plusieurs écritures, les adresses à écrire doivent être connues. Il se peut en effet qu’une écriture dont on ne connait pas l'adresse aliase la lecture. La garantie est liée à la présence de la file de µops mémoire ou la file de µops STORE. Si une seule écriture a une adresse inconnue, elle reste dans la file de µops mémoire et bloque l'émission de toutes les autres micro-opérations mémoire, lecture comme écriture.
Mais il existe des techniques dites spéculatives, qui modifient l'ordre des accès mémoire sans se soucier de leurs dépendances. Elles émettent les lectures dès que leurs opérandes sont prêts, sans se préoccuper de l'aliasing avec une écriture précédente. Pour éviter tout problème, le processeur vérifie si aucune dépendance mémoire n'est violée et corrige le tir dès qu'une violation est détectée. Le processeur vérifie s'il a fait une erreur de prédiction, en vérifiant les adresses à écrire ou lire. En cas d'erreur, le processeur fait comme lors d'une prédiction de branchement ratée : il remet le pipeline en ordre et ré-exécute les accès mémoire dans le bon ordre. Il s'agit d'un cas particulier d’'''exécution spéculative''', au même titre que la prédiction de branchement.
Toutes les techniques de ce genre exécutent des lectures dans le désordre. L'avantage est que cela permet d'exécuter les lectures en avance. Vu que la donnée lue est l'opérande d'autres instructions, les exécuter en avance fait prendre de l'avance à un paquet d'instructions. Mais il faut comparer cet avantage au risque de mauvaise prédiction. Et aussi bizarre que cela puisse paraître, cela donne de bons résultats. Il faut dire que les situations où une lecture relit une donnée tout juste écrite sont rares : même pas 2 à 3 % des lectures. Les dépendances mémoire sont donc très rares, les cas d'aliasing sont vraiment limités. En termes de cout en transistors, le hardware qui vérifie les dépendances mémoire reste globalement le même à peu de choses près, il est juste déplacé dans le pipeline et adapté à sa nouvelle fonction.
===La file de lectures===
La première technique que nous allons voir reprend la technique du ''Load Ordering, Store Ordering'', avec ses deux files de µops de lecture et d'écriture. Elle exécute les lectures sans se préoccuper des dépendances avec les écritures. Mais le processeur vérifie qu'aucune violation de dépendance n'a eu lieu après un certain temps. Une violation de dépendance peut avoir lieu pour n'importe quelle instruction précédant la lecture dans l'ordre du programme. En clair, le processeur doit conserver les lectures spéculatives tant que les instructions précédentes ne sont pas terminées. Pour cela, il faut ajouter une nouvelle file d'attente, qui mémorise les lectures terminées. Nous l’appellerons la file de vérification des lectures, ou encore la '''file de lectures'''.
Elle fonctionne sur le même principe que la file d'écriture et les deux font partie de l'unité mémoire. Les deux visent à remettre les lectures/écritures dans l'ordre du programme, afin que les instructions suivant une lecture/écriture fautive soient annulées. Les lectures sont insérées dans la file de lecture à l'émission, quand elles quittent la file de µops mémoire/LSQ, ce qui est la même chose avec les écritures pour la file d'écriture. Une différence très importante est que les écritures sont retardées alors que les lectures s'exécutent immédiatement. D'ailleurs, la file de lecture ne sert pas pour le contournement : les données lues sont immédiatement envoyées au chemin de données, pas besoin de les contourner ultérieurement. Alors que la file d'écriture est utilisée pour le contournement en cas de dépendances RAW.
Les lectures sortent de la file µops LOAD dès que leurs adresses sont calculées, sans tenir compte des dépendances mémoires. Elles sont alors ajoutées à la file de lectures et s'exécutent immédiatement, de manière spéculative. Pour vérifier qu'il n'y a pas d'erreur de spéculation, le processeur conserve l'adresse de la lecture effectuée dans la file de lectures. Les lectures quittent la file de lecture quand toutes les instructions précédentes dans l'ordre du programme sont terminées, à savoir tant quand elles ont quitté le tampon de réordonnancement/ROB. Notons que la file de vérification des lectures contient les lectures dans l'ordre du programme. En effet, les lectures sont accumulées en sortant de la file de µops LOAD, où elles sont triées dans l'ordre du programme. Et cela permet de gérer les erreurs de prédiction parfaitement, en annulant les instructions suivant une erreur de prédiction.
[[File:Spéculation avec une file de lecture.jpg|centre|vignette|upright=2.5|Spéculation avec une file de lecture.]]
Reste à détecter les erreurs de prédiction, ce qui peut être fait avec deux méthodes.
Avec la première, les violations de dépendances mémoire sont faites à chaque écriture. Précisément, la détection a lieu quand une écriture est sur le point de quitter la file d'écriture. L'unité mémoire récupère alors l'adresse d'écriture et la compare avec toutes les lectures dans la file de vérification des lectures. Si aucune correspondance n'est trouvé, c'est que les lectures spéculatives n'ont accédé à l'adresse d'écriture. Mais s’il y a correspondance, c'est qu'une lecture a lu l'adresse avant qu'elle soit écrite. Pour cela, la file de vérification des lectures est un mélange entre FIFO et cache/mémoire associative. La vérification fait cependant gaffe à ne prendre en compte que les lectures situées après l'écriture dans l'ordre du programme (seulement ces instructions sont capables de générer une dépendance RAW).
Avec la seconde méthode, les violations de dépendance mémoire sont détectées au moment où la lecture quitte à la file de lectures et le ROB. Mais cela demande que la donnée lue soit recopiée dans la file de lecture. Quand la lecture quitte la file de lecture et le ROB, la lecture est ré-exécutée et la donnée relue. Le processeur compare alors la donnée lue à ce moment avec la donnée mémorisée dans la file de lecture. Si la donnée est différente, alors il y a eu erreur de violation de dépendance mémoire. Évidemment, relire une donnée n'est pas gratuit. Il est possible de limiter son impact en performance en dédiant un port de lecture sur le cache rien que pour ça. Mais le cout en circuit est comparable à l'usage d'une FIFO associative de la technique précédente. Mais quoi qu'il en soit, le cache doit permettre des lectures en un seul cycle, sans quoi la méthode ne marche pas vraiment.
===La prédiction de dépendances mémoires===
Certains processeurs essayent de prédire si deux accès mémoires sont dépendants : ils incorporent une unité qui va fonctionner comme une unité de prédiction de branchement, à la différence qu'elle prédit les dépendances mémoires. Si cette unité prédit que deux accès mémoires sont indépendants, le processeur les exécute dans le désordre, et les exécute dans l'ordre du programme dans le cas contraire. Il faut prendre en compte les erreurs de prédiction, ce qui est fait comme pour les mauvaises prédictions de branchement : on vide le pipeline.
Beaucoup de techniques de prédiction des dépendances mémoire ont été inventées et en faire une revue exhaustive serait long et fastidieux. On pourrait citer les ensembles colorés (''color sets''), et bien d'autres techniques. Mais nous n'allons parler que des techniques les plus élémentaires. Quoi qu’il en soit, la recherche sur le sujet est assez riche, comme toujours quand il s'agit d'architecture des ordinateurs.
On peut améliorer cette technique en mémorisant les instructions qui ont causé une mauvaise prédiction de dépendance mémoire. La prochaine fois qu'on exécute ces instructions, on sait qu'il y a de grandes chances qu'il se produise une erreur de prédiction, ce qui pousse à ne pas les exécuter de manière spéculative. On peut pour cela réutiliser les techniques de prédiction de branchement, tels des compteurs à saturations, mis à jour en cas d'erreurs de prédiction de dépendances mémoire. Dans tous les cas, on trouve un cache, équivalent au ''branch target buffer'', qui mémorise les instructions fautives (leur ''Program Counter''), avec d'autres informations comme les adresses des instructions dépendantes. La première classe de techniques du genre consiste à mémoriser les lectures qui ont causé une violation de dépendance, tandis que l'autre ne mémorise que les écritures. Cette dernière est l'approche utilisée par le '''cache de barrières d’écriture''' (store barrier cache).
Enfin, nous allons conclure avec une dernière technique : celle des '''ensembles d’écritures''' (store sets). Cette technique est capable de gérer le cas où une lecture dépend de plusieurs écritures : le processeur mémorise l'ensemble des écritures avec lesquelles la lecture a déjà eu une dépendance. Cet ensemble d'écritures associées à une lecture est appelé tout simplement un ensemble d’écritures, et reçoit un identifiant (un nombre) par le processeur. Cette technique demande d'utiliser deux tables :
* une qui assigne un identifiant d’ensemble d’écritures à l'instruction en cours ;
* une autre qui mémorise les ensembles d’écritures sous la forme d'une liste chainée : le début de la liste correspond à l'écriture la plus ancienne de l’ensemble. Cela permet d'obtenir l'écriture la plus ancienne dans cet ensemble d’écritures directement.
Quand le processeur détecte une violation de dépendance entre une lecture et une écriture, elle ajoute l'écriture dans l’ensemble d’écritures adéquat. Le déroulement d'une lecture demande d'accéder à la première table pour récupérer l'identifiant de l’ensemble d’écritures, et l'utiliser pour adresser la seconde table. S'il y a dépendance, cet accès renvoie l'écriture fautive en cas de dépendance. Quand une écriture est envoyée à l'unité mémoire, celle-ci va accéder à la table de correspondances de la même manière qu'une lecture, et va récupérer l'identifiant de l’ensemble d’écritures auquel elle appartient, identifiant qui est utilisé pour vérifier s'il y a une écriture en cours d’exécution. Si c'est le cas, cela signifie que l'écriture en cours d’exécution doit s’exécuter avant l'écriture qui a consulté la table : cette dernière est mise en attente.
==La prédiction d'adresse et de valeur==
D'autres techniques de prédiction mémoire plus élaborées que les précédentes existent. Il s'agit de la prédiction d'adresse et de la prédiction de valeur. Leur nom trahit ce qu'elles font. La première technique tente de prédire l'adresse d'une lecture/écriture, alors que la seconde tente carrément de prédire quelle sera la donnée lue.
Il y a bien de la recherche académique sur le sujet, mais il s'agit de techniques assez osées, dont on voit mal comment elles pourraient fonctionner. L'efficacité de cette technique a été étudiée grâce à des simulateurs. Suivant les études ou les programmes, on trouve des résultats qui varient pas mal. Dans certains cas, la performance baisse un peu, et dans d'autres, on peut avoir des gains de plus de 10% ! Mais dans des cas normaux, on trouve des gains de 4-5% environ. Ce qui est tout de même pas mal à l'heure actuelle.
Cependant, en Janvier 2025, à l'heure où j'écris ces lignes, il a été découvert la première utilisation de ces deux techniques dans un processeur commercial. La découverte fait suite à la publication de deux vulnérabilités matérielles sur les CPU Apple M2 et M4 et quelques CPU ARM, qui font justement usage de ces deux techniques. Les deux vulnérabilités, appelées SLAP et FLOP, sont assez similaires aux attaques Spectre et Meltdown, sauf qu'elles attaquent non pas la prédiction de branchement, mais la prédiction d'adresse et de valeur du CPU.
===La prédiction d'adresse===
La prédiction d'adresse tente de prédire l'adresse d'une lecture à l'avance, ce qui permet de lancer les lectures en avance. Si la prédiction est correcte, on gagne quelques cycles qui permet aux unités de calcul de faire leurs calculs plus en avance, sans compter que les mécanismes de désambiguïsation mémoire fonctionnent plus efficacement. Si jamais cette adresse est connue plus tôt, on détecte les dépendances plus rapidement et on peut agir en conséquence.
Des variantes de ces unités de prédiction d'adresse sont utilisées pour précharger des données depuis le cache de donnée. Et leur efficacité est assez bonne, voire excellente dans certains cas. La différence est que la prédiction d'adresse est utilisée pour prédire les adresses à lire depuis le cache vers les registres, ce qui est le sujet de cette sous-partie.
La prédiction d'adresse est réalisée par une sorte de cache, qui associe à chaque instruction la prochaine adresse à lire. Nous appellerons ce cache la '''table de prédiction d'adresse'''. Pour faire l'association instruction <-> adresse lue/écrite, le tag du cache utilise l'adresse de l'instruction (le ''program Counter''), alors que la ligne de cache associée contient l'adresse prédite. Son contenu est utilisé pour faire une prédiction. Lorsqu'une lecture est émise, la table est consultée pour récupérer en avance l'adresse à lire, avant que celle-ci soit calculée par l'ALU. La lecture est alors exécutée en avance, avant que la vraie adresse soit connue, en utilisant l'adresse prédite.
Reste qu'il faut des circuits autour de cette table de prédiction d'adresse, pour déterminer si la prédiction est correcte, ainsi que pour remplir cette table avec une adresse prédite. Pour cela, il y a un circuit de prédiction d'adresse dédié. Il récupère les instructions qui quittent le ROB ou la file des lectures mémoire, afin de vérifier les lectures qui viennent de se terminer/''commit'' et qui quittent le pipeline. Il peut contenir une '''table d'apprentissage''', qui mémorise des informations nécessaires pour faire des prédictions d'adresse. Par exemple, un historique des dernières adresses lues pour les lectures les plus récentes, ou un historique des adresses des lectures. La table est aussi utilisée pour déterminer si les prédictions effectuées étaient correctes ou non. Pour cela, il est possible de réutiliser les techniques vues dans le chapitre sur la prédiction de branchement, notamment des compteurs à saturation.
Pour la prédiction de branchement et les autres formes de prédiction mémoire, les table d'apprentissage et pour les instructions/adresses sont fusionnées en une seule. Mais ici, il est nécessaire de les séparer pour une raison assez particulière : éliminer la pollution du cache dans la table de prédiction d'adresse. On ne peut pas allouer une ligne de cache pour chaque nouvelle instruction de lecture exécutée. Seule une minorité des lectures profitent de la prédiction d'adresse. Il est nécessaire de filtrer les lectures pour ne conserver que celles qui profitent de la prédiction d'adresse, ce qui est possible plus facilement en séparant la table d'apprentissage et les compteurs à saturation de la table de prédiction d'adresse.
Une première méthode consiste à prédire que la lecture lit toujours la même adresse, ce qui lui vaut le nom de '''prédiction d'adresse constante'''. On pourrait se demander pourquoi une telle situation surviendrait. La raison est qu'il arrive que les compilateurs n'arrivent pas à gérer les accès mémoires efficacement. Dans certaines situations un peu compliquées impliquant des pointeurs ou des références, divers phénomènes complexes d'''aliasing'' des pointeurs peuvent générer des relectures intempestives de données en mémoire. Cela peut aussi arriver lorsqu'on compile du code qui provient de plusieurs librairies, ou quand du code lit régulièrement des constantes en mémoire ROM.
La prédiction d'adresse constante ajoute un circuit qui analyse les dernières lectures, une fois qu'elles quittent le ROB, qu'elles ''commit''. Si, pour une instruction de lecture, l'adresse lue est toujours la même, alors l'instruction est ajoutée dans la table de prédiction d'adresse. Vu que les instructions qui accèdent toujours à la même adresse sont rares, il est préférable d'initialiser les compteurs à saturation de façon à ce qu'ils disent que toute nouvelle instruction change d'adresse. Il s'agit d'une méthode simple mais peu efficace, car cette situation est rare.
Une autre méthode détecte les accès en enjambées, là encore quand les lectures quittent le ROB. Nous l’appellerons la '''prédiction d'adresse en enjambées'''. De tels accès surviennent régulièrement quand on accède des tableaux ou d'autres structures de données semblables, ce qui rend la technique très intéressante. Implémenter cette technique est facile : il suffit d'ajouter un circuit qui détecte les accès en enjambées, au lieu de celui qui détecte les accès constants. À chaque fois qu'une lecture entraine un succès de cache dans la table de prédiction d'adresse, l'enjambée est ajoutée à l'adresse contenue dans la table de prédiction d'adresse, l'ancienne adresse prédite est remplacée par celle avec l'enjambée d'ajoutée.
Sur les Apple M2-M4, la prédiction d'adresse gére aussi bien les adresses constantes que les accès en enjambée. Le brevet qui décrit l'implémentation de l'unité de prédiction d'adresse et de valeur est potentiellement le suivant : [https://patents.google.com/patent/US11829763B2/en Early load execution via constant address and stride prediction]. En théorie, il est possible de faire mieux en repérant des accès mémoires qui se répètent de façon régulière et cyclique, même s'ils n'ont aucune enjambée. Ce genre d'accès se trouve assez souvent lorsque l'on manipule des listes chainées ou des structures de données assez irrégulières comme des arbres, des graphes, etc. Mais il n'y a pas encore de CPU qui implémente de telle optimisation.
===La prédiction de valeur : prédire la donnée lue===
Les techniques de '''prédiction de valeur''' (''Value Prediction'') consistent à prédire quelle est la donnée qui sera lue par une instruction de lecture. Oui, vous avez bien lu : le processeur est capable de parier sur la valeur qui sera chargée depuis la mémoire, de prédire si cette valeur vaut 0, 1, 1024, etc. Une fois son pari fait, il exécute les instructions du programme avec la valeur qu'il a pariée de façon spéculative. Si le pari est correct, le CPU continue l’exécution. Sinon, il est obligé de vider le pipeline et de recommencer avec la bonne valeur.
Au premier abord, cette technique semble franchement mal partie tellement les chances de se tromper sont tellement énormes ! Mais le fait est que, dans certains cas, il est possible de spéculer correctement. Là encore, les techniques les plus simples fonctionnent dans deux cas. Le premier est celui où une donnée est relue à l'identique à chaque fois que la lecture est exécutée. Le second est celui où la donnée est incrémentée/décrémentée d'une constante qui est toujours la même d'une exécution de la lecture à l'autre. Une sorte d'enjambée constante, mais pour les données.
Nous allons nous concentrer sur le premier cas, celui où une donnée est relue plusieurs fois sans être modifiée entre temps. L'idée de base est qu'une instruction de lecture qui s'exécute plusieurs fois de suite à la même adresse renvoie la même valeur à chaque fois. C'est parfaitement possible s’il n'y a eu aucune écriture à cette adresse entre-temps, d'où le fait qu'il faille surveiller si les prédictions constantes sont correctes.
On peut se demander quelles sont les raisons qui font qu'une instruction de lecture renvoie la même valeur à chaque fois. Après tout, autant lire une seule fois la donnée et la garder dans un registre une bonne fois pour toutes ! Mais dans la réalité, on fait face à quelques limites, qui seront détaillées dans la liste suivante. La première est que le manque de registre fait que certaines données sont conservées en RAM et sont relues fréquemment. Cela arrive quand on stocke des constantes en mémoire. Par exemple, sur les processeurs x86, les constantes flottantes ne peuvent pas être intégrées dans nos instructions via le mode d'adressage immédiat. À la place, on les stocke en mémoire et on les charge dans les registres à chaque fois qu'on en a besoin.
L'implémentation est similaire à la prédiction d'adresse, si ce n'est que c'est la donnée lue qui est prédite. Là encore, on a une '''table de prédiction de donnée lue''', consultée lorsqu'une lecture est émise. La table de prédiction de la donnée lue est une mémoire cache qui associe le ''Program Counter'' de la lecture à la donnée prédite, éventuellement couplé à des compteurs à saturation. Elle est consultée à chaque fois qu'une lecture est émise, la donnée adéquate est récupérée lors d'un succès de cache. On trouve aussi un circuit qui insère ou retire des instructions de la table de prédiction de donnée lue. Elle détecte les lectures prédictibles, couplé à une table d’apprentissage, et quelques circuits annexes liés au ROB ou aux files de lectures. Elle contient au minimum des compteurs à saturations, associés chacun à une instruction (son ''program counter'' pour être précis).
==La désambiguïsation mémoire a des effets de bord==
La désambiguïsation mémoire a une caractéristique que l'exécution dans le désordre simple n'a pas : elle a des effets qui débordent sur la mémoire. Il est possible de savoir si un processeur fait de la désambiguïsation mémoire en regardant le bus mémoire. On s’aperçoit alors que les lectures se font dans un ordre différent de celui du programme. À l'opposé, l'exécution dans le désordre n'a pas d'effets observables de ce genre. Elle améliore la performance, elle modifie les durées observables des instructions, mais cela se limite à des questions de ''timings''. Le seul moyen de savoir si un processeur fait de l'exécution dans le désordre est de mesurer les latences/durées de chaque instruction. Mais aucun état observable au-delà des ''timings'' n'est modifié. Pas avec la désambiguïsation mémoire.
===La consistance mémoire===
Le fait que la désambiguïsation mémoire ait des effets observables ne pose aucun problème avec un seul processeur, ca elle est conçue pour. Par contre, les techniques de désambiguïsation mémoire peuvent poser des problèmes avec plusieurs processeurs. Il arrive que des programmes partagent une même portion de mémoire, et lisent/écrivent dedans. Avec plusieurs processeurs, chaque processeur effectue la désambiguïsation mémoire dans son coin sans se préoccuper des autres. Les opérations d'écriture peuvent être mises dans le désordre du point de vue des autres processeurs, et les lectures effectuées par les autres processeurs peuvent alors renvoyer de vielles données.
Un exemple classique est celui de deux cœurs qui partagent le même cache ou la même mémoire, mais qui utilisent chacun une file d'écriture (''store buffer''). Imaginons que le premier cœur effectue une écriture, puis une lecture, à des adresses différentes. L'écriture est retardée grâce au tampon d'écriture, alors que la lecture lit directement le cache. Pour le premier processeur, l'écriture s'est réalisée avant la lecture, car pour lui, la fin de l'écriture signifie écrire dans le tampon d'écriture. Mais pour le second cœur, l'ordre s'est inversé : il voit la lecture dans le cache, puis l'écriture dans le cache. La raison est qu'il n'a pas connaissance du contenu du tampon d'écriture de l'autre cœur.
Un autre exemple : les techniques de prédiction d'adresse et de valeur posent des problèmes avec les dépendances RAW. Elles permettent à une instruction mémoire de s'exécuter avant une autre, malgré la présence d'une dépendance RAW entre les deux. L'ordre entre les deux instructions change, alors que les autres processeurs ne devraient pas le voir. Et dans les faits, les processeurs qui incorporent ces techniques ont des modèles de consistance mémoire qui ne permettent pas ces optimisations en cas de dépendances RAW. Les deux techniques doivent idéalement être désactivées pour les instructions mémoire qui ont des dépendances RAW.
Les programmeurs doivent tenir compte de ce genre de situations, dans des cas assez rares et liés à la programmation bas niveau. Pour cela, ils doivent savoir comment le processeur réorganise les accès mémoire. Concrètement, chaque processeur définit un '''modèle de consistance mémoire''' qui indique quelles optimisations sont activées et non. Il s'agit d'une sorte de contrat entre le logiciel et le matériel : les programmeurs savent comment le processeur va réorganiser les lectures et écritures et peuvent coder leurs programmes en fonction. L'implémentation du système de consistance mémoire dépend grandement de l'architecture, mais des modèles ont été standardisés afin de permettre aux programmeurs de savoir où placer les barrières mémoires. Il s'agit de vrais standards, formalisés, parfois mathématiquement, que des chercheurs étudient sur un plan mathématique et algorithmique.
Le modèle préféré des programmeurs est la '''consistance séquentielle''', où les différents processeurs effectuent des accès mémoire dans l'ordre du programme et les accès mémoire simultanés sont interdits. La première contrainte interdit l'usage de mécanismes de désambiguïsation mémoire, la seconde interdit l'usage de caches non-bloquants. La consistance séquentielle a donc un cout en performance assez important, ce qui fait que les processeurs modernes n'utilisent pas la consistance séquentielle.
Un modèle plus flexible est le '''''total store ordering''''', qui autorise la présence d'une file d'écriture et l'usage de caches non-bloquants pour les lectures (interdit d'avoir plusieurs écritures simultanées). C'est plus ou moins celui utilisé par les processeurs x86, comme on le verra plus bas. Il existe des modèles de consistance mémoire plus relâchés, appelés des '''modèles de consistance faible''', qui autorisent diverses optimisations de spéculation mémoire, la combinaison d'écriture (fusionner deux écritures à la même adresse en une seule), etc. Ils sont supportés sur les architectures ARM et POWER.
===Les barrières mémoire===
Le changement d'ordre des lectures et écritures peut poser occasionnellement des problèmes avec la programmation pour les architectures multi-processeur ou multicœurs. Il existe des morceaux de code qui ne marchent que si la consistance séquentielle est respectée et il faut faire en sorte qu'ils marchent sur des processeurs avec un modèle de consistance mémoire différent. Pour cela, les processeurs incorporent des instructions appelées '''barrières mémoires''' ou '''''Fences'''''. Elles forcent le processeur à terminer tous les accès mémoire qui précédent dans l'ordre du programme. Certains processeurs possèdent une barrière mémoire pour les lectures et une autre pour les écritures. D'autres processeurs ajoutent des barrières mémoires dédiées à des cas particuliers. Elles permettent de forcer les accès à des données partagées à dérouler correctement.
[[File:Fences.png|centre|vignette|upright=2|Fences.]]
Leur implémentation est assez simple : elles empêchent l'émission de toute nouvelle instruction mémoire, tant qu'une condition précise n'est pas réunie. Par exemple, la barrière mémoire pour les écritures attend que la file d'écriture soit vidée avant de démarrer le moindre accès mémoire.
Un compilateur peut placer les barrières mémoires au bon endroit, avec un peu d'aide du programmeur. Certains langages de programmation permettent d'indiquer au compilateur qu'une donnée doit toujours être lue depuis la mémoire RAM, via le mot-clé volatile. C'est très utile pour préciser que cette donnée est potentiellement partagée par plusieurs processeurs ou manipulable par des périphériques. Les compilateurs peuvent placer des barrières mémoires lors des lectures ou écritures sur ces variables, mais ce n'est pas une obligation.
Il arrive aussi que le programmeur doive manipuler explicitement des barrières mémoires. Utiliser l'assembleur est alors une possibilité, mais qui est rarement exploitée, pour des raisons de portabilité. Pour limiter la casse, certains systèmes d'exploitations ou compilateurs peuvent aussi fournir des barrières mémoires explicites, encapsulées dans des bibliothèques ou cachées dans certaines fonctions.
===La modèle de consistance des processeurs x86===
Après avoir vu la théorie, passons maintenant à la pratique. Dans cette partie, on va voir les modèles de consistances utilisés sur les processeurs x86, ceux qu'on retrouve dans les PC actuels. Le modèle de consistance des processeurs x86 a varié au cours de l'existence de l'architecture : un vulgaire 486DX n'a pas le même modèle de consistance qu'un Core 2 duo, par exemple. Ils ont évolués au cours du temps, avec l'arrivée de nouvelles optimisations. De plus, leur formalisation a été progressive, et s'est faite au fur et à mesure.
En général, les modèles de consistance des processeurs x86 ont toujours étés assez restrictifs, si on les compare aux autres processeurs. Il s'agit de modèles de type ''total store ordering'' (TSO), au moins dans les grandes lignes. Ils permettent donc d'effectuer certaines lectures en avance, soit parce que les lectures se font à des adresses différentes, soit par utilisation de réacheminement écriture vers lecture. Les possibilités d'exécution en avance des lectures varient suivant le processeur, elles deviennent de plus en plus performantes avec le temps.
Le premier modèle de consistance est apparu sur les premiers processeurs x86 et est resté en place sur tous les processeurs de marque Pentium. Les processeurs de l'époque n'incorporaient pas beaucoup d'optimisations, le budget en transistor n'était pas assez conséquent pour ça, la mémoire n'était pas assez lente. Le modèle TSO de l'époque n'autorisaient que peu de lectures anticipées. Une lecture ne peut être exécutée en avance que sous les conditions suivantes :
* les écritures contournées se font dans la mémoire cache ;
* la lecture doit se faire dans la mémoire RAM ;
* les écritures se font à une adresse différente de la lecture ;
* aucune transaction avec un périphérique ne doit être en cours.
A partir du Pentium 4, les choses changent. Le Pentium 4 est en effet le premier processeur à implémenter des techniques permettant d’exécuter plusieurs processus en parallèle, dont l'Hyperthreading. En conséquence, le modèle de consistance a dû être assoupli pour éviter de perdre bêtement en performance. Il s'agit d'un vrai modèle de type TSO, la majeure partie des contraintes sur les lectures anticipées sont réduites au minimum. Il y a aussi des contraintes supplémentaires pour les instructions dites atomiques, ainsi que pour les instructions qui accèdent aux périphériques ou aux entrées-sorties. Une optimisation séparée du TSO est que les écritures en mémoire peuvent changer d'ordre dans un cas exceptionnel : les écritures effectuées par les instructions de copie mémoire comme REPMOVSD, REPSCACB, ainsi que les instructions SSE MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, MOVNTPD. Les écritures effectuées dans ces instructions peuvent se faire dans un désordre complet (ou presque).
Lors du passage au 64 bits, le modèle mémoire des processeurs x86 de l'époque a été formalisé dans un papier d'Intel appelé [https://www.cs.cmu.edu/~410-f10/doc/Intel_Reordering_318147.pdf Intel® 64 Architecture Memory Ordering White Paper], mis à jour en 2008 suite à quelques problèmes techniques. La formalisation du modèle a été d'une grande aide aux programmeurs, qui devaient se débrouiller avec des documentations qui ne formalisaient pas de modèle de consistance précis. Le document décrit un modèle de consistance mémoire portant le nom barbare de ''total lock order + causal consistency” (TLO+CC)'', qui admet plus d'optimisations que le modèle TSO. Mais la description était en réalité légèrement différente du modèle mémoire réellement implémenté sur les processeurs existants, qui utilisaient du TSO, sauf en de rares occasions.
Les concepteurs de processeur font souvent cela, à savoir définir un modèle de consistance plus laxiste que réellement implémenté, pour des questions de comptabilité. Ainsi, si les futurs processeurs de la marque implémentent un modèle plus laxiste, les programmes déjà codés continuent de fonctionner. Le modèle décrit dans la documentation laisse de la marge pour le futur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le scoreboarding et l'algorithme de Tomasulo
| prevText=Le scoreboarding et l'algorithme de Tomasulo
| next=Le parallélisme mémoire
| nextText=Le parallélisme mémoire
}}
</noinclude>
3vfbarqm1bqi0v3x7f0wgfwgnkz5o8g
Fonctionnement d'un ordinateur/L'exécution dans le désordre
0
65869
773347
765278
2026-09-27T20:26:44Z
Mewtow
31375
/* Les fenêtres d'instruction décentralisées */
773347
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons parlé des techniques d'émission dans l'ordre. L'idée est d’exécuter une nouvelle instruction à chaque cycle, dans des unités de calcul séparées. Les instructions sont émises consécutivement et l'unité d'émission bloque l'émission en cas de dépendance, mais les instructions s’exécutent en parallèle dans des unités de calcul séparées. De plus, elles peuvent se finir dans le désordre, en absence de dépendances, si les exceptions sont imprécises. Le point important est qu'on doit exécuter des instructions indépendantes dans des unités de calcul séparées.
Les techniques d'exécution dans le désordre que nous allons voir dans ce chapitre sont dites à '''émission dans le désordre'''. Elles se distinguent des précédentes sur un point précis : l'unité d'émission bloque l'émission d'une instruction, elle seule est bloquée, les instructions suivantes peuvent poursuivre. L'émission des instructions peut donc se faire dans le désordre, dans le sens où une instruction peut être émise alors qu'une instruction précédente ne l'est pas encore.
L'avantage de l'émission dans le désordre est qu'une instruction s'exécute dès que ses opérandes sont disponibles. Le processeur incorpore de quoi déterminer quand un résultat est disponible, de quoi savoir quand un opérande est prête. Par prête, on veut dire présente soit dans le réseau de contournement, soit enregistrée dans les registres. Dès qu'une instruction a ses opérandes disponibles, elle est émise et exécutée. C'est là la seule contrainte : les dépendances WAR, WAW et autres sont gérées par un ROB ou toute autre technique d'exception précise, seules les dépendances RAW limitent l'exécution.
: Dans la suite, nous utiliserons parfois l'abréviation OOO pour parler d'exécution dans le désordre. L'abréviation est celle de ''Out Of Order Excution''.
Mais avant de poursuivre, précisons une chose importante : l'exécution dans le désordre traite les accès mémoire à part. La majorité des processeurs OOO assez anciens exécutent les accès mémoire dans l'ordre, seules les instructions entières/flottantes/branchements sont exécutées dans le désordre. L'exécution dans le désordre des accès mémoire est courante sur les processeurs récents, mais c'est l'unité d'accès mémoire qui se charge de changer l'ordre des accès mémoire toute seule dans son coin. Nous allons volontairement mettre de côté la gestion des accès mémoire dans ce chapitre.
==Les fenêtres d’instruction : généralités==
Pour implémenter l'exécution dans le désordre, il faut ajouter une sorte de mémoire qui met en attente les instructions bloquées, à émettre. Les instructions attendent dans cette mémoire le temps que leurs opérandes soient disponibles. De plus, il faut un circuit capable de gérer les dépendances RAW. L'unité d'émission qui bloquait les instructions, ne bloque plus rien, car elle faisait office de dépendance structurelle qui est éliminée.
[[File:File de micro-opération.png|vignette|upright=1|File de micro-opérations]]
Les processeurs sans exécution dans le désordre ont une unité d'émission qui bloque tout le pipeline dès qu'une instruction ne peut pas être émise. Pour limiter l'impact de ce blocage, quelques rares processeurs incorporent une mémoires FIFO appelée la '''file d'instruction''', aussi appelée '''file de micro-opération'''. Elles permettent d'émettre des instructions dans l'ordre, à savoir qu'elles quittent la file d'instruction dans l'ordre d'ajout, dans l'ordre de décodage. Le schéma ci-dessous illustre une telle file d'attente couplée à un ''scoreboard''.
Les techniques modernes d'OOO utilisent aussi des files de micro-opération améliorées, comme on le verra plus bas. De telles files de micro-opération permettent d'émettre des instructions dans le désordre, ce qui corrige le problème mentionné plus haut avec le ''scoreboard''. Si une instruction bloque le pipeline, les instructions suivantes peuvent être émises.
Il existe dans les grandes lignes 4 grandes techniques d'exécution dans le désordre. Elles peuvent se classer sur deux critères : est-ce que les instructions sont émises dans l'ordre, et combien il y a de files de micro-opération.
{|class="wikitable"
|-
!
! Emission dans l'ordre
! Emission dans le désordre
|-
! Une file de micro-opération
| ''Scoreboard''
| Fenêtre d'instruction centralisée
|-
! Plusieurs files de micro-opération
| Plusieurs FIFOs, plusieurs ''Scoreboard'' indépendants
| Fenêtre d'instruction décentralisée
|}
===L'usage de plusieurs files d'instruction===
La première méthode évoluée d'OOO utilise plusieurs files de micro-opération. L'idée est qu'une fois décodée, les instructions sont accumulées dans des files de micro-opération différentes. Il y a typiquement une file de micro-opération pour les opérations entières, une autre pour les opérations flottantes, une autre pour les accès mémoire. D'autres processeurs utilisent une file pour les accès mémoire et une autre pour les autres instructions, comme le faisait le Pentium 4 d'Intel.
Les files de micro-opération sont des mémoires FIFO, ce qui fait qu'elles conservent l'ordre des instructions. Et chaque FIFO est couplée à un ''scoreboard'' qui vérifie les dépendances à chaque cycle. La présence de ''scoreboard'' nous dit que l'intérêt n'est pas un mécanisme d'émission plus compliqué, mais que la technique se repose sur le fait d'avoir plusieurs files de micro-opération. L'intérêt d'avoir plusieurs FIFOs est que si une dépendance bloque une FIFO, les autres peuvent continuer d'émettre des instructions.
Les files de micro-opération émettent leurs instructions de manière indépendante, ou presque. Elles ne savent pas si telle instruction dans une autre file doit passer avant ou après l'instruction qu'elles émettent. Par contre, les unités d'émission communiquent entre eux pour gérer la disponibilité des registres. Quand un registre est lu ou écrit par une instruction, les autres files d'instruction doivent être prévenues et mettre à jour leurs unités d'émission. La logique d'émission est donc plus complexe que prévu. Le processeur Pentium 4 avait deux FIFOs séparées : une pour les instructions d'accès mémoire et une autre pour les autres instructions. Si jamais une instruction mémoire bloquait le pipeline, l'autre FIFO pouvait continuer à exécuter des instructions entières indépendante de la lecture bloquée.
Les FIFOs sont simples à implémenter et ont un cout en circuit modéré. Par contre, le gain en performances est assez limité avec cette méthode, surtout comparé aux méthodes qui vont suivre. Le problème est qu'il est facile de se retrouver avec toutes les files de micro-opération bloquées. Quand l'une est bloquée, les autres tendent à se vider rapidement, particulièrement quand c'est la file pour les accès mémoire qui se bloque. Les techniques qui vont suivre font totalement disparaitre ce genre de blocage.
===La fenêtre d'instruction centralisée===
Les processeurs OOO modernes utilisent une file d'attente modifiée, où les instructions sont insérées dans l'ordre, mais peuvent sortir dans le désordre, être émises dans le désordre. La file d'attente en question est appelée la '''fenêtre d'instruction'''. Les instructions décodées sont accumulées dans la fenêtre d'instruction, où elles attendent que leurs opérandes soient disponibles. La fenêtre d'instruction sert donc de file d'attente. A chaque cycle, l'unité d'émission consulte la fenêtre d'instruction pour voir quelles instructions peuvent s'exécuter, quelles instructions ont leur opérandes de disponibles. Elle choisit une instruction exécutable et l'envoie aux ALU. La fenêtre d'instruction doit spécialement être conçue pour, c'est une sorte de mémoire associative très complexe.
[[File:OOO Issue.png|centre|vignette|upright=2.5|Exécution dans le désordre avec une fenêtre d'instruction]]
Le cas le plus simple n'utilise qu'une seule fenêtre d'instruction. Avec elle, l'unité d'émission, détecte les dépendances et répartit les instructions sur les unités de calcul. Les instructions décodées sont ajoutées en parallèle dans la fenêtre d'instruction, et le ROB. La raison est que les instructions quittent la fenêtre d'instruction dans le désordre, l'ordre des instructions est perdu après l'unité de décodage/renommage de registre. Aussi, pour remplir le ROB, la seule opportunité est en sortie de l'unité de décodage. Dans le cas où une instruction est décodée en plusieurs micro-opérations, elles sont toutes insérées en même temps dans la fenêtre d'instruction et le ROB.
[[File:Fenêtre d'instruction.png|centre|vignette|upright=2|Fenêtre d'instruction.]]
Les micro-opérations mémoire sont à part des autres, pour diverses raisons. Une de ces raisons est que les dépendances de données sont classées en deux types : les dépendances de registre et les dépendances d'adresse. Et les deux sont fondamentalement différentes. Autant on peut détecter les dépendances de registre lors de l'émission, autant c'est plus compliqué avec les dépendances d'adresse. De plus, rappelons que si les lectures peuvent s'exécuter dans le désordre, les écritures doivent s'exécuter dans l'ordre, ou du moins en donner l'illusion. Pour cela, le processeur remet les écritures dans l'ordre avec une sorte de mini-ROB intégré à l'unité mémoire, appelée la file d'écriture. Elle complémente le ROB, qui lui ne gére que les écritures dans les registres, mais les deux collaborent.
Les processeurs grand public anciens n'autorisaient pas d'exécution dans le désordre des accès mémoire. Pour cela, l'unité mémoire est précédée par une file de micro-opérations, pour forcer l'exécution dans l'ordre des accès mémoire. Les processeurs modernes ajoutent des techniques d'exécution dans le désordre des accès mémoire, mais nous les verrons dans un chapitre dédié. Dans les deux cas, les micro-opérations mémoire doivent être envoyées à l'unité mémoire dans l'ordre du programme, dans l'ordre de décodage. Dans ce chapitre, on suppose que l'unité mémoire est précédée d'une file de micro-opération mémoire dédiée, rien que pour elle. Elle est à part du reste.
[[File:Processeur avec émission dans l'ordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans l'ordre des accès mémoire]]
===Les fenêtres d'instruction décentralisées===
Une autre solution utilise plusieurs fenêtres d'instruction. Pour faire la différence avec l'usage d'une fenêtre d'instruction unique, nous allons parler de fenêtre d'instruction centralisée s'il n'y en a qu'une dans le processeur, de '''fenêtre d'instruction décentralisée''' s'il y en a plusieurs. Dans le cas général, chaque fenêtre d'instruction est associée à un type précis d'opération/instruction. La tendance moderne est d'utiliser une fenêtre d'instruction pour les instructions de calcul entières, une autre pour les flottantes. Il s'agit d'un choix simple et efficace, mais quelques processeurs font autrement, pour répondre à des contraintes très diverses.
L'usage de fenêtres décentralisées simplifie la répartition des instructions sur les différentes unités de calcul. Par exemple, utiliser des fenêtres séparées pour les ALU et les FPU facilite la répartition des instructions de calcul sur les ALU. Une fenêtre centralisée demande de faire la différence entre instruction entière/flottante, puis de choisir sur quelle unité de calcul entière/flottante utiliser. Une fenêtre décentralisée fait les deux choix séparément : d'abord on envoie l'instruction dans la bonne fenêtre suivant si c'est une instruction flottante ou entière, et ensuite on gère la disponibilité des ALUs/opérandes. Et on peut adapter la même idée en séparant les unités d'accès mémoire des ALU entières, etc.
Par contre, un défaut est que certaines fenêtres d'instruction peuvent être sous-utilisées. Par exemple, la station de réservation flottante est inutilisée si le programme n'exécute pas d'opérations flottantes. Ou encore, les fenêtres pour les accès mémoire peuvent être sous-utilisées, si le programme effectue peu d'accès mémoire relativement aux autres instructions. En fait, tout dépend de comment sont calibrées les fenêtres d'instruction et de la répartition des instructions dans le programme entre opérations entières, flottantes, mémoire. Par exemple, si un programma de deux fenêtres d'instructions identiques, une pour les calculs entiers et une autre pour les calculs flottants, elles ne seront utilisées à la perfection que si la moitié des instructions du programme fait des calculs flottants et l'autre des calculs entiers. Toute déviation par rapport à cette répartition idéale risque de laisser des entrées vides dans une fenêtre d’instruction.
Un point important est que l'émission est découpée en deux étages avec des fenêtres d'instruction décentralisées. La première étape envoie l'instruction décodée vers la fenêtre d'instruction adéquate, la seconde gère l'émission proprement dit. La première est l'étage de ''dispatch'' qui envoie l'instruction dans la fenêtre d’instruction adéquate, selon que c'est une instruction flottante, entière, autre. La seconde est l'étage de ''scheduling'', qui gère la disponibilité des opérandes et des unités de calcul. En clair, on trie les instructions suivant leur type, suivant le type d'unité de calcul adéquate, puis on l'émet proprement dit. Faire ainsi a plusieurs avantages, avec cependant peu d'inconvénients.
Lors de l'étape de ''dispatch'', l'instruction émise est ajoutée au tampon de réordonnancement, s'il existe. Sans cela, impossible de conserver l'ordre des instructions. Les instructions sont émises dans l'ordre au niveau de l'unité de ''dispatch'', pas au niveau de l'étage de ''scheduling''. Aussi, pour remplir le ROB, cela doit se faire au dernier moment où les instructions sont émises dans l'ordre, soit en sortie de l'unité de ''dispatch''. De plus, les micro-opérations mémoire sont traitées à part des autres, elles sont envoyées directement à l'unité mémoire. La gestion des calculs d'adresse est quelque peu complexe, mais ils peuvent être fait soit dans l'unité mémoire, soit dans les ALU entières, peu importe. Il y a aussi un système de contournement complexe entre ALU/FPU et unité mémoire, qui n'est pas représenté dans le schéma ci-dessous.
[[File:Processeur avec plusieurs fenêtres d'instruction.png|centre|vignette|upright=2.5|Processeur avec plusieurs fenêtres d'instruction.]]
Il faut noter que quelques processeurs utilisent des méthodes intermédiaires entre les trois solutions précédentes. Par exemple, les processeurs AMD Zen utilisent une fenêtre d'instruction décentralisée un peu particulière. Les unités de calcul entières disposent d'une fenêtre d'instruction rien que pour elles, sur ce processeur. Les instructions flottantes sont quant à elles alimentées par une file de micro-opération, pas une fenêtre d'instruction ! La raison à ce choix est un compromis entre performance et cout en circuit. L'exécution dans le désordre est peu efficace sur les instructions flottantes, car les CPU n'ont que peu d'ALU flottantes séparées. Vu le cout en circuits d'une fenêtre d'instruction, les concepteurs du processeur ont préféré utiliser une file de micro-opération pour les opérations flottantes.
===Un cas extrême : les fenêtres d'instruction totalement décentralisées===
Prenons un cas extrême de fenêtre d'instruction décentralisée : celui où chaque ALU entière a sa propre station de réservation. En clair : une fenêtre d'instruction par ALU/FPU ! On parle de '''fenêtres d'instruction totalement décentralisées'''. Vous vous dites que ce cas est un peu trop extrême pour être réaliste, mais il n'en est rien. C'est même très courant sur les architectures basse consommation, et sur pas mal de CPU x86 modernes.
Un cas aussi extrême entraine une légère perte de performance, comparé à une fenêtre d'instruction centralisée ou partiellement décentralisée. Mais elle entraine aussi un gain considérable en termes de consommation énergétique. Et c'est la raison pour laquelle les fenêtres d'instructions totalement décentralisées sont utilisées sur les architectures basse consommation. Voyons pourquoi.
La perte de performance a plusieurs raisons. Mais la principale est que
Ou encore, une station de réservation peut avoir plusieurs µops prêtes, et une autre zéro, ce qui fait qu'une seule. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
===Avantages et inconvénients de chaque implémentation===
Un processeur contient soit une fenêtre d'instruction unique, soit plusieurs fenêtres d'instruction séparées. Typiquement, les fenêtres d’instruction séparées sont spécialisées, dans le sens où on a une fenêtre pour les instructions flottantes, une autre pour les instructions entières, une autre pour les accès mémoire, etc. Pour résumer, on a le choix entre une grosse fenêtre généraliste et plusieurs fenêtres spécialisées, sauf que la fenêtre centralisée est lente et complexe, contrairement aux fenêtres spécialisées. Les deux méthodes ont des inconvénients et des avantages différents.
Dans les grandes lignes, l'avantage des fenêtres décentralisées est qu'elles sont plus petites qu'une grosse fenêtre d'instruction. Cela simplifie la gestion du contournement et de la répartition des instructions sur les unités de calcul, comme on le verra dans quelques paragraphes. Elles sont donc moins complexes et plus rapides. L'autre avantage, c'est qu'il est possible de démarrer l'exécution de plusieurs instructions simultanément : une instruction par fenêtre décentralisée, contre une par fenêtre d'instruction.
Mais les fenêtres décentralisées sont souvent sous-utilisées, partiellement remplies, contrairement aux fenêtres d'instruction. Il arrive qu'une fenêtre d'instruction soit remplie, alors que les autres sont vides. Par exemple, prenons un processeur avec une fenêtre d'instruction reliée à la FPU, et une autre reliée aux autres ALUs. Si le processeur n'exécute que des opérations flottantes, la fenêtre reliée à la FPU sera pleine, alors que l'autre sera vide. L'exécution des instructions dans le désordre est alors limitée par la petite taille de la fenêtre, qui ne peut plus accepter de nouvelle instruction flottante. Avec une fenêtre unique, on n'aurait pas eu ce problème : on aurait eu une énorme fenêtre d'instruction remplie d'instruction flottante, au lieu d'une petite fenêtre spécialisée.
Il est maintenant temps de voir en quoi sont faites les fenêtres d’instruction, qu'elles soient centralisées ou décentralisées. Nous allons aussi voir quelle est la logique d'émission associée.
==L'implémentation d'une fenêtre d'instruction : généralités==
Une fenêtre d'instruction est une mémoire qui mémorise des micro-opérations. Elle est cependant plus complexe qu'une mémoire RAM ou qu'une FIFO et ressemble un petit peu à une mémoire cache. Elle dispose d'un port d'écriture et d'un port de lecture, au minimum.
Le port d'écriture permet à l'unité de décodage d'insérer une micro-opération dedans, d'ajouter la micro-opération qui vient d'être décodée. Si une instruction machine est décodée en plusieurs micro-opération, deux possibilités. La première est de les insérer un par un, ce qui fait qu'on peut se contenter d'un seul port d'écriture. L'autre possibilité est de les ajouter en même temps, ce qui demande plusieurs ports d'écriture.
Le port de lecture sert à émettre une instruction. On part du principe que la fenêtre d'instruction ne peut émettre qu'une seule instruction à la fois, car les processeurs que nous avons vu précédemment sont de ce type. Mais nous verrons dans quelques chapitre qu'il existe des processeurs dits superscalaires, qui sont capables d'émettre plusieurs instructions par cycle. Dans ce cas, la fenêtre d'instruction a un seul port de lecture. Un processeur superscalaire, quant à lui, doit avoir un port de lecture par unité de calcul, ce qui fait beaucoup plus.
===Les entrées d'une fenêtre d’instruction===
Les fenêtres d'instruction sont composées d''''entrées''', des mots mémoire qui stockent une instruction. Une instruction réserve une entrée après son décodage et la libère dès qu'elle est envoyée aux unités de calcul. Une entrée contient l'opcode (les signaux de commande à envoyer à l'ALU), le registre de destination du résultat, les registres de chaque opérande, et éventuellement des signaux de commande en plus. De plus, chaque opérande est couplée à un bit de disponibilité, qui indique si elle est disponible. Quand un opérande est écrit dans les registres, le bit de présence correspondant est mis à jour. Enfin, chaque entrée possède un bit ''empty'' qui indique si elle est vide, cette information étant utile pour réserver des entrées.
[[File:Entrée d'une station de réservation - sans les tags.png|centre|vignette|upright=3|Entrée d'une fenêtre d'instruction]]
Nous verrons dans quelques chapitres que certaines fenêtres d'instruction sont capables de mémoriser les opérandes des instructions. De telles fenêtres d'instruction seront appelées, dans ce cours, des '''stations de réservation'''. Les entrées des stations de réservation sont assez semblables à celle des fenêtres d'instruction, à un détail près : les opérandes de l'instruction sont enregistrées directement dans des champs séparés. Il y a deux champs, un par opérande, pour stocker l'opérande elle-même, lue depuis les registres ou obtenue via le réseau de contournement. Notons que les champs pour les opérandes viennent en plus des champs pour les noms de registres, encore que les deux peuvent être fusionnés si on veut optimiser le tout.
[[File:Entrée d'une station de réservation.png|centre|vignette|upright=3|Entrée d'une station de réservation.]]
Précisons que la terminologie n'est pas très fiable. Les termes "fenêtre d'instruction" et "station de réservation" ne regroupent pas du tout la même chose d'un papier de recherche à l'autre, d'un bouquin à l'autre, d'un chercheur à l'autre, d'un ingénieur à l'autre, d'un professeur à l'autre. Le terme initial vient pourtant de l'article original sur l'algorithme de Tomasulo, où il désignait une fenêtre d'instruction associée à une unité de calcul précise. Mais le terme a ensuite été réutilisé, ce qui fait que certains processeurs avec plusieurs unités de calcul sont décrit comme ayant une unique station de réservation. En bref : c'est le bazar ! Mais nous en reparlerons dans le chapitre sur le renommage de registres, car c'est la place idéale pour les aborder.
Toujours est-il que j'ai fait le choix de confondre les concepts de fenêtre d'instruction et de station de réservation avec les concepts de '''lecture avant émission''' et de '''lecture après émission'''. La différence entre les deux est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation. Dans le cas "avant émission", les opérandes sont mémorisées dans la station de réservation. Et cette différence a une influence sur le pipeline du processeur, le banc de registres et tout ce qui s'en suit. Un désavantage de la lecture avant émission est qu'il faut stocker les opérandes après lecture, ici dans les stations de réservation. Mais un avantage est que le banc de registre a besoin de moins de ports de lecture, car une bonne partie des opérandes sont fournies par le système de contournement. Nous verrons cela dans le détail dans le chapitre sur les processeurs superscalaires.
Il a été proposé de fusionner le tampon de ré-ordonnancement avec la fenêtre d'instruction dans une structure unique. La méthode a notamment été utilisée sur les processeurs d'architecture K6 d'AMD. Le tout donnait une structure appelée le '''DRIS''' (''deferred scheduling register renaming instruction shelf''). La différence avec une fenêtre d’instruction normale est que les entrées sont libérée non pas quand l'instruction est exécutée/émise, mais quand elle termine son exécution et quitte le ROB. La fenêtre d'instruction doit alors incorporer des bits pour savoir si telle entrée a émis sont instruction ou non.
===La logique d'éveil et de sélection===
Dans le cas qui va suivre, on suppose que le processeur ne peut émettre qu'une instruction à la fois. Chaque cycle, une instruction est sélectionnée et est envoyée dans le chemin de données, soit à une ALU, soit à une unité d'accès mémoire. La sélection d'une instruction est effectuée par l'unité d'émission, qui est aussi appelée le '''''scheduler'''''. Elle gère la disponibilité des opérandes et la disponibilité des unités de calcul adéquate. Le choix de l'instruction émise doit être le plus pertinent possible, pour des raisons de performances.
Pour le premier point, l'unité d'émission détermine quelles instructions sont candidates pour l'émission. Les ''instructions candidates'' sont celles dont tous les opérandes sont prêts. Le processeur contient un circuit qui détecte les instructions candidates : la '''logique d’éveil''' (''wake-up logic''). Une fois que la logique d'éveil a fait son travail, une instruction est choisie pour s’exécuter en fonction de la disponibilité des unités de calcul adéquates. Ce rôle est dévolu à un circuit qu'on appelle la '''logique de sélection''', ou ''select logic''.
[[File:Fonctionnement complet de la logique d’émission.jpg|centre|vignette|upright=1.5|Fonctionnement complet de la logique d’émission.]]
La logique de sélection doit gérer le fait que les unités de calcul du processeur ne sont pas toutes identiques. Déjà, un processeur a généralement des unités séparées pour les instructions entières et flottantes, des unités spécialisées dans les accès mémoire, parfois des unités spécialisées pour les branchements. Une instruction doit être attribuée à la bonne unité de calcul, on ne doit pas envoyer une instruction entière dans une unité de calcul flottante. De plus, parmi les unités de calcul entières, toutes ne sont pas identiques. Par exemple, il est fréquent d'avoir plusieurs unités de calcul spécialisées dans les opérations simples (additions, soustractions, opérations logiques) avec une unité pour les multiplications/divisions séparées, parfois une unité spécialisée pour les décalages/rotations, etc. Là encore, allouer la bonne instruction à la bonne ALU est complexe. Plus les unités de calcul sont hétérogènes, plus la logique de sélection est compliquée.
Les logiques d'éveil comme de sélection sont assez complexes. Comme on le verra plus bas, la logique de sélection la plus simple, qui gère une seule unité de calcul, est basée sur un encodeur à priorité couplé à d'autres circuits. Et pour gérer plusieurs ALUs, il faut mettre des encodeurs en cascade. Le résultat est que même en prenant une logique de sélection simple, elle sera assez lente et gourmande en circuits. Aussi, la logique de sélection met souvent un cycle d'horloge entier pour faire son travail, elle a son propre étage de pipeline attribué. La logique d'éveil est dans le même cas, ce qui fait qu'émettre une micro-opération demande deux cycles d'horloge.
Le défaut est que cela rajoute des cycles d'horloges entre la fenêtre d'instruction et l'ALU, ce qui complique la gestion du pipeline. Ces cycles de retard sont à prendre en compte au moment d'émettre les instructions, les ''scheduler'' doit en tenir compte pour des performances optimales. Sans tenir compte de ces cycles de retard, les instructions arrivent en retard de quelques cycles à l'ALU, cycles de retard durant lesquels l'ALU n'a pas fait de calcul et a été sous-utilisée, sans compter que le système de contournement n'est pas utilisé au mieux. Et le problème est encore plus important sur les processeur avec des stations de réservation, qui utilisent la "lecture après émission" : cela rajoute un cycle d'horloge en plus, un temps de retard en plus.
===La logique d'éveil : l'émission anticipée===
La logique d'éveil doit tenir compte du fait qu'il y a plusieurs étages entre l'émission et l'exécution sur une unité de calcul. Par exemple, un processeur a souvent entre un et à cinq étages entre la file d'attente et les unités de calcul. Il faut lire les registres, gérer le contournement, et cela peut prendre quelques cycles d'horloge. Sur le Pentium 4, on trouve 6 étages entre la fenêtre d’instruction et l'entrée de l'ALU. Si les micro-opérations sont émises quand les opérandes sont disponibles, il y aura un délai de quelques cycles avant qu'elles atteignent l'ALU. Alors qu'idéalement, on souhaiterait que la micro-opération atteigne l'ALU dès que ses opérandes sont disponibles, pour profiter des techniques de contournement. Pour cela, la logique d'éveil émet les instructions quelques cycles en avance pour qu'elles arrivent au bon moment. Il s'agit d'une technique d''''émission anticipée''', dont nous avions parlé dans le chapitre sur le contournement.
: Il faut noter que le problème se pose plus avec des fenêtres d'instruction qu'avec des stations de réservation. Les premières ont un étage en plus entre émission et ALU, vu qu'il faut lire des registres après l'émission.
L'émission anticipée demande d'émettre un signal d'éveil en avance de quelques cycles, pile au bon moment. Par exemples, si on a deux étages entre l'ALU et l'unité d'émission, le signal de réveil sera généré deux cycles avant que le résultat soit effectivement calculé. L'implémentation varie, mais deux solutions sont possibles. La première est de générer le signal d'éveil dans l'ALU, ce qui n'a de sens que pour les opérations comme les multiplications ou les divisions, qui prennent plusieurs cycles. Une autre technique génère le signal de réveil dans une structure centralisée, spécialisée dans la génération des signaux d'éveil. L'unité de composée d'un registre à décalage par registre. Imaginons qu'une instruction est émise, que cette instruction mette N cycles à s'exécuter, et qu'elle écrive dans un registre de destination. L'unité sélectionne alors le registre à décalage associé au registre de destination, et met le bit numéro N à 1. Le registre décalé d'un cran à chaque cycle. Quand le bit sortant est un 1, alors le signal de validité pour l'opérande est généré.
L'émission anticipée marche bien si l'instruction a une durée fixe, connue à l'avance. Et autant c'est le cas pour les instructions arithmétiques et logiques, autant ce n'est pas le cas pour les accès mémoire. Les accès mémoire ont une latence variable : quelques cycles pour un succès de cache, plusieurs dizaines de cycles pour un défaut de cache L1, pas loin de la centaine pour un défaut de cache L2, etc. Et cela pose des problèmes pour l'émission anticipée.
L'émission en avance pour les accès mémoire est purement spéculative : le processeur émet des instructions en supposant que la lecture entrainera un succès de cache, quitte à annuler l'instruction en cas de défaut de cache. Mais faire ainsi demande non seulement d'annuler les instructions émises en avance, mais aussi de ne pas retirer les instructions émises en avance de la fenêtre d'instruction. Elles sont émises, mais une copie de sauvegarde est conservée dans la fenêtre d'instruction. Si le processeur détecte un défaut de cache, il peut alors ré-exécuter l'instruction. S'il détecte un succès de cache, les instructions lecture-dépendante sont retirées de la fenêtre d'instruction, la copie de sauvegarde est devenue inutile et est donc jetée.
===La logique de sélection : âge ou position ?===
La logique de sélection est primordiale pour de bonnes performances. Il existe deux méthodes principales pour sélectionner l'instruction à exécuter. La première est la plus simple : elle se base sur lé numéro de l'entrée. En effet, une fenêtre d'instruction est avant tout une sorte de mémoire, un mix entre mémoire associative, mémoire RAM, et FIFO. Et chaque entrée a un numéro, une adresse mémoire. Nous parlerons de numéro dans ce qui suit, car ce sera plus simple, le terme d'adresse mémoire étant peut-être un peu exagéré dans le contexte des fenêtres d'instruction.
Toujours est-il que la fenêtre d'instruction est adressable dans le sens où on peut lui envoyer le numéro de l'entrée voulue, et la fenêtre d’instruction renvoie le contenu de l'entrée sur son port de lecture. Et c'est ce que fait la logique de sélection : elle génère le numéro de l'entrée à lire. Le numéro est alors envoyé sur l'entrée d'adresse de la fenêtre d’instruction, et elle fournit la micro-opération associée sur son port de lecture, son '''port d'émission'''.
La première méthode de sélection, donc. Elle est basée sur sur le numéro de l'entrée. Si plusieurs entrées sont éveillées, la logique de sélection prend celle avec le plus petit numéro. Elle porte le nom de '''sélection par position''', sous-entendu position dans la fenêtre d'instruction. Vous l'avez peut-être deviné, mais la logique de sélection est alors un encodeur à priorité. Le circuit de sélection ne se résume donc pas seulement à un encodeur à priorité, mais c'est le circuit central. L'avantage d'une telle méthode de sélection est qu'elle est simple à implémenter : un encodeur à priorité est un circuit connu, facile à implémenter. Mais on remarque un défaut : un encodeur est un circuit assez rapide, mais qui utilise beaucoup de transistors, beaucoup de portes logiques, surtout si on veut qu'il soit rapide. Ce qui explique que la logique de sélection ait son propre étage de pipeline rien que pour elle.
La seconde méthode s'appelle la '''sélection par âge''', au nom assez transparent. L'idée est de privilégier l'émission de l'instruction la plus ancienne. Elle demande que la fenêtre d'instruction trie les micro-opérations par ordre d'insertion dans la fenêtre d'instruction, ce qui en fait une pseudo-FIFO. Le tri est partiel, car les instructions peuvent sortir de la fenêtre d’instruction à tout moment. Compacter la fenêtre d'instruction, à savoir regrouper toutes les entrées valides est une idée, mais elle est compliquée à implémenter. Aussi, elles préfèrent encoder des informations sur l'ordre d'émission dans chaque entrée. L'encodeur à priorité doit alors travailler non pas sur des nombres entiers liés à l'ordre d'insertion dans la pseudo-FIFO. Elle est encore plus gourmande en circuits que la méthode précédente.
Les processeurs haute performance utilisent souvent des méthodes hybrides. Par exemple, les processeurs AMD de microarchitecture Bulldozer utilisaient une méthode intermédiaire. Ils utilisaient une fenêtre d'instruction couplée à un encodeur à priorité pour la sélection par position, mais complémentaient le tout avec une unité qui mémorisait l'instruction la plus ancienne dans la fenêtre d’instruction. Le mécanisme de sélection par âge était utilisé en priorité. Mais si l'instruction la plus ancienne n'était pas prête, le processeur basculait sur le mécanisme de sélection par position.
===La compaction des fenêtres d'instruction===
À chaque cycle, les instructions décodées sont ajoutées dans la fenêtre d'instruction, dans des entrées vides. Vu que les instructions quittent celle-ci dans le désordre, ces vides sont dispersés dans la fenêtre d'instruction, ce qui pose problème pour déterminer où placer les nouvelles instructions. La solution la plus triviale consiste à conserver une liste des vides, mise à jour à chaque insertion ou émission d'instruction. Une autre solution consiste à éliminer les vides en compactant la fenêtre d'instruction à chaque cycle d'horloge. Des circuits se chargent de détecter les vides et de regrouper les instructions en un unique bloc. Il faut signaler que certaines processeurs arrivent à se passer de cette étape de compactage, mais au prix de fenêtres d'instruction nettement plus complexes.
Autre problème : quand il faut choisir quelle instruction émettre, il y a toujours plusieurs candidats. Si on choisit mal, des instructions restent en attente trop longtemps parce que d'autres instructions plus jeunes leur passent devant. Pour éviter cela, les instructions les plus vielles, les plus anciennes, sont prioritaires. Pour cela, on peut utiliser une FIFO un peu spéciale pour la fenêtre d'instruction. Si les ajouts d'instruction se font dans l'ordre, les instructions ne quittent pas forcément la fenêtre d'instruction dans l'ordre imposé par une FIFO : les instructions restent triées dans leur ordre d'ajout, même s'il y a des vides entre elles. Dans ces condition, il est préférable que le compactage conserve l'ordre FIFO des instructions. Dans ces conditions, l'instruction la plus ancienne est celle qui est située à l'adresse la plus faible : le circuit de sélection peut donc être fabriqué avec des encodeurs, et est relativement simple.
==Les fenêtres d'instruction basées sur une matrice de dépendances==
L'implémentation la plus simple étend le fonctionnement d'un pipeline dynamique usuel. Rappelez-vous le chapitre sur les pipeline dynamiques. Nous avions vu que de tels pipelines émettaient une instruction par cycle, quitte à émettre des bulles de pipeline en cas de dépendance bloquante. Et il est possible d'améliorer leur unité d'émission pour qu'elle soit compatible avec l'exécution dans le désordre.
===Rappels sur le registre de réservation/disponibilité===
L'émission d'une instruction est gouvernée par un ''registre de réservation'' qui indique quels registres sont réservés par une instruction, et ceux qui sont libres. Un registre réservé est un registre qui sera écrit par une instruction en cours d'exécution, mais qui n'a pas encore écrit son résultat. Les instructions candidates doivent avoir leurs opérandes dans des registres libres. Sinon, c'est signe que leurs opérandes sont réservées en écriture, donc pas encore écrites, donc pas disponibles. Si une instruction candidate veut lire ce registre, c'est signe qu'il y a une dépendance RAW : elle veut lire un registre réservé, qui est destiné à être écrit par une instruction en vol. Si elle veut écrire, c'est signe qu'elle veut écrire dans un registre où une autre instruction veut écrire : c'est une dépendance WAW. Dans les deux cas, l'instruction n'est pas émise.
Le registre de réservation encode les registres réservés comme suit : chaque bit est associé à un registre. Le bit numéro 0 est associé au registre numéro 0, le bit numéro 1 au registre numéro 1, etc. Là, En clair, les numéros de registres réservés sont encodés en représentation non pas binaire, mais en représentation ''one-hot'' (vue au premier chapitre).
{|class="wikitable"
|+ Registre de réservation
|-
! Registre 7 !! Registre 6 !! Registre 5 !! Registre 4 !! Registre 3 !! Registre 2 !! Registre 1 !! Registre 0
|-
| 0 || 0 || 1 || 0 || 0 || 1 || 0 || 0
|}
Avant d'émettre une instruction, l'unité d'émission extrait les registres opérande/destination et les convertis dans la même représentation que le registre de réservation, à savoir en représentation ''one-hot''. Le circuit de traduction binaire vers ''one-hot'' est, pour rappel, un simple décodeur. Puis, on fait un OU logique entre les sorties des décodeurs. Le résultat est un masque qui indique quels registres sont lus par l'instruction, encodé en représentation ''one-hot''. Appelons-le le '''masque d'opérandes'''. Le masque est alors comparé au registre de réservation pour vérifier si l'instruction peut être émise.
[[File:Unité d'émission simple, dans l'ordre.png|centre|vignette|upright=2|Unité d'émission simple, dans l'ordre]]
Si l'émission est autorisée, le registres de réservation est là aussi mis à jour. Le registre de réservation est aussi mis à jour dès qu'une instruction enregistre son résultat dans les registres, ou alors dès qu'il est disponible pour le contournement. Le bit associé au registre repasse alors à 0 (ou à 1). Sans contournement, la mise à jour se fait alors à la toute fin de l'instruction, quand elle se termine, lors de la dernière étape d'enregistrement, à la fin de son pipeline. Avec, elle se fait quand l'instruction quitte l'unité de calcul.
===L'extension à l'exécution dans le désordre : l'éveil par matrice de bits===
La différence entre un pipeline dynamique et l'exécution dans le désordre est que l'on passe d'une instruction à émettre à plusieurs. Plusieurs instructions sont mises en attente dans une fenêtre d’instruction, et y attendent leur tour. L'idée est alors d'envoyer le registre de disponibilité à toutes les entrées, à toutes les instructions en attente. Ainsi, on sait quelles sont les instructions qui peuvent s'exécuter et celles qui ne le peuvent pas. L'implémentation est assez simple, elle ressemble beaucoup à l'implémentation d'un pipeline dynamique simple, si ce n'est que des circuits sont dupliqués.
L'implémentation utilise des fenêtres d'instruction légèrement différentes de celles introduites plus haut. Elles n'utilisent notamment pas de bits de disponibilité, mais autre chose. Lorsqu'une instruction est ajoutée à la fenêtre d'instruction, le masque de disponibilité des opérandes est stocké dans la fenêtre d'instruction. A chaque cycle, le registre de disponibilité est envoyée à toutes les entrées, et est comparé avec tous les masques de disponibilité des opérandes. Si une comparaison renvoie un 1, alors l'instruction de l'entrée est une instruction candidate à l'émission.
[[File:Unité d'émission dans le désordre basée sur un registre de disponibilité.png|centre|vignette|upright=2.5|Unité d'émission dans le désordre basée sur un registre de disponibilité]]
L'ensemble est implémenté avec l'aide d'une '''matrice de bits''', où chaque ligne correspond à une entrée et chaque colonne à un registre. À chaque croisement entre une ligne et une colonne, on trouve un bit. Si le bit de la ligne n et de la colonne m est à 1, cela veut dire : l'instruction dans l'entrée n a besoin de lire le registre m. S’il est à 0, alors cela veut dire que l'instruction stockée dans la ligne n n'a pas besoin de la donnée dans le registre m.
[[File:Planificateur à matrice de bits.jpg|centre|vignette|upright=2|Planificateur à matrice de bits.]]
Lorsqu'une instruction réserve une entrée, elle initialise la ligne en fonction des registres qu'elle souhaite lire. Pour vérifier si une instruction a ses opérandes prêts, le processeur compare le registre de disponibilité à la ligne associée à l'instruction/entrée. A chaque intersection ligne-colonne, se trouve un comparateur de 1 bit, qui détecte si le registre est demandé et disponible. Les résultats de cette porte sont ensuite envoyés à une porte ET, qui fait le gros du travail.
[[File:Logique de détection de la disponibilité d'une instruction.jpg|centre|vignette|upright=2|Logique de détection de la disponibilité d'une instruction.]]
==Les fenêtres d'instruction basées sur une mémoire associative==
Les processeurs modernes utilisent une mémoire associative pour la fenêtre d'instruction. Les mémoires associatives ont été abordées il y a quelques chapitres, mais faisons quelques rappels. Les mémoires associatives servent à accélérer la recherche de données dans un ensemble. Or, une fenêtre d'instruction vérifie régulièrement si chaque entrée est prête. Il s'agit d'un processus de recherche d'une instruction qui respecte une condition bien précise dans la mémoire, ce qui fait que les mémoires associatives sont donc tout indiquées.
Comme vous le savez, les signaux de commande d'une instruction sont propagés avec celle-ci dans le pipeline, le nom du registre de destination ne faisant pas exception. Le nom de registre est envoyé à la fenêtre d'instruction lors de l'écriture du résultat dans les registres. S'il y a correspondance, l'opérande est disponible et son bit de disponibilité est mis à 1. On peut adapter cette méthode pour tenir compte du contournement assez simplement.
[[File:Détection des dépendances par propagation du registre de destination.png|centre|vignette|upright=2|Détection des dépendances par propagation du registre de destination.]]
En conséquence, le circuit de détection des dépendances est constitué d'un grand nombre de comparateurs : un par champ « nom de registre » dans chaque entrée. La logique d'éveil/''wake up'' regroupe tous les comparateurs, la logique de sélection est un circuit situé en-dehors de la mémoire associative. Les entrées sont dans la mémoire associative elle-même.
[[File:Gestion des bits de validité.png|centre|vignette|upright=2|Gestion des bits de validité.]]
L'implémentation la plus simple est celle du schéma précédent, où on utilise une mémoire associative unique. Une version plus élaborée sépare la fenêtre d'instruction en deux parties : une mémoire qui mémorise les registres des opérandes, une autre mémoire pour le reste. La mémoire pour les registres opérandes est la mémoire associative proprement dite, c'est elle qui est associées aux comparateurs, à la logique de ''wake-up''/réveil, etc. Par contre, l'autre mémoire est une mémoire RAM simplifiée, qui mémorise les informations qui n'ont pas besoin des comparateurs. L'opcode de l'instruction est là-dedans, par exemple.
===Les optimisations de la consommation d'énergie : la désactivation des portions inutilisées===
Le problème avec des fenêtres d'instruction de ce type est leur forte consommation énergétique, ainsi que le grand nombre de portes logiques utilisées, qui deviennent prohibitif pour des fenêtres d'instruction un peu grosses. Pour résoudre ce problème, certains ont optimisé les comparateurs. D'autres ont tenté de profiter du fait que la majorité des bits dans les entrées sont composés de zéros. Bref, les optimisations purement matérielles sont légion.
Une première solution est de désactiver les entrées qui sont inutilisées, vides, sans instruction. Pour cela, on fait précéder les comparateurs par un circuit qui les connecte ou déconnecte des lignes de bit. Le circuit est commandé par le bit ''empty'' qui indique si une entrée est vide ou non. L'avantage est que la consommation d'énergie de la fenêtre d'instruction devient alors proportionnelle au nombre d'entrées non-vides, et non au nombre d'entrées total. Du moins, en apparence, les entrées non-utilisées doivent quand même être alimentées pour fonctionner, mais l'activité de comparaison disparait dans les entrées vides. Il est possible d'étendre cette technique aux entrées dont les opérandes sont prêtes. Les gains sont généralement assez bons, avec une réduction de consommation variant entre 30 et 50%, avec une implémentation très simple et très économe en circuits.
Une autre solution, assez simple à mettre en place, consiste à désactiver une partie de la fenêtre d'instruction sin elle est inutilisée. Pour cela, il faut segmenter la fenêtre d'instruction avec la technique du ''wire partitionning''. Pour rappel, une fenêtre d'instruction est une mémoire associative. Et comme toute mémoire associative, elle contient des fils, les lignes de bit, qui relient les entrées/sorties à toutes les cellules mémoire. Plus une ligne de bit est longue, plus elle consomme d'énergie et plus le temps de lecture/écriture est long.
L'idée est de segmenter les lignes de bits en plaçant des répéteurs qui recopient la tension d'un segment au suivant. Les répéteurs sont des tampons trois-états, ce qui permet de déconnecter les portions inutilisées de la ligne de bit. Ce faisant, la fenêtre d'instruction est découpée en segments, qui peuvent être désactivés si besoin. Si il y a peu d'instructions chargées dans la fenêtre d'instruction, les portions inutilisées sont désactivées. La technique marche d'autan mieux si les instructions sont compactées au début de la fenêtre d'instruction.
[[File:Wire partitionning.png|centre|vignette|upright=2|Wire partitionning]]
Tout le problème est de savoir quelles portions désactiver. La technique est assez simple si la fenêtre d'instruction est compactée, à savoir si toutes les instructions sont régulièrement regroupées au début de la mémoire CAM. Mais même avec cette optimisation, la décision de désactiver une portion de la fenêtre d'instruction n'est pas à prendre à la légère. Il ne faut pas désactiver une portion contenant des instructions pouvant être émise sous peu. La décision dépend de paramètres variés : proportion d'entrée vides, quantité d'entrées récemment activées/désactivées récemment, présences d'entrées avec un ''match'' d'opérandes récent, etc.
Il est important de préciser que les techniques en question fonctionnent aussi sur les autres types de fenêtre d'instruction, qui ne sont pas basées sur des mémoires associatives. Nous allons voir celles-ci dans la suite du chapitre, mais gardez à l'esprit que ces optimisations, à savoir désactiver les entrées vides/prêts et partitionner la fenêtre d'instruction, sont des optimisations générales qui marchent avec presque tout.
===L'ajout d'une FIFO pour les instructions déjà prêtes à l'émission===
D'autres techniques permettent de réduire aussi bien le cout en circuits que la consommation d'énergie de la fenêtre d'instruction. L'idée est d'utiliser une mémoire associative quand elle est réellement nécessaire, mais d'utiliser une fenêtre d'instruction "normale" dès que possible.
La méthode la plus simple tient compte du fait que certaines instructions ont déjà toutes leurs opérandes de disponibles à l'émission, mais doivent quand même être mises en attente, parce que l'ALU adéquate est occupée, parce que des dépendances WAW/WAR sont encore là, ou pour tout autre raison. Dans ce cas, elles sont mises en attente non pas dans la fenêtre d'instruction, mais dans une mémoire FIFO toute simple. L'idée est qu'au lieu d'avoir une énorme mémoire associative pour la fenêtre d'instruction, on déplace quelques entrées dans une petite FIFO annexe. Le résultat est un gain en terme d'énergie et de circuits, au prix d'une perte de performance mineure. La perte de performance en question se manifeste si aucune instruction n'a ses opérandes de prêtes à l'émission : le programme n'utilise pas la FIFO et voit alors une fenêtre d'instruction plus petite.
Une amélioration de cette technique tient compte du fait que certaines instructions sont dans un cas intermédiaire. Elles ont déjà une opérande de prête lors de l'émission, mais pas l'autre. Le nombre d'opérandes varie suivant l’instruction : certaines n'ont besoin que d'un seul opérande. De plus, il arrive qu'une opération tout juste décodée ait déjà un ou plusieurs de ses opérandes de prêts. Cette constatation permet d'éviter d'utiliser des comparateurs pour les opérandes déjà prêts. Par exemple, on peut parfaitement utiliser trois fenêtres d'instruction :
* une pour les instructions dont tous les opérandes sont prêts, mais qui sont quand même mises en attente, sans comparateur ;
* une pour les instructions dont un seul opérande manque, qui n'utilise qu'un comparateur par entrée ;
* une pour les instructions dont deux opérandes manquent à l'appel, qui utilise deux comparateurs par entrée.
C'est beaucoup plus économique que d'utiliser une seule grosse fenêtre d'instruction qui contiendrait autant d'entrées que les trois précédentes réunies, avec deux comparateurs par entrée. Certains sont même allés plus loin, et ont proposé de supprimer la fenêtre d'instruction avec deux opérandes par entrée. Les instructions dont deux opérandes sont inconnus au décodage sont stockées dans la fenêtre d'instruction pour instructions avec un comparateur par entrée. Un circuit de prédiction se charge alors de prédire l'opérande manquant, cette prédiction étant vérifiée plus loin dans le pipeline. Il serait cependant étonnant qu'une telle proposition ait donné lieu à la moindre implémentation réelle.
===Les optimisations de la consommation d'énergie avancées===
D'autres chercheurs ont conservé une fenêtre d'instruction unique, avec autant de comparateurs par entrée qu'il y a d'opérandes possibles par instruction. Simplement, le processus de détection des opérandes prêts est légèrement ralenti : on ne vérifie qu'un opérande par cycle. Pour vérifier les deux opérandes d'une entrée, on doit attendre deux cycles.
Enfin, certains chercheurs ont proposé des fenêtres d'instruction segmentées. Les instructions circulent à chaque cycle d'un segment vers le suivant : en conséquence, certains segments conservent les instructions les plus anciennes, un autre les instructions les plus jeunes, etc. Seul le denier segment, celui qui contient les instructions les plus vielles, peut émettre une instruction : la détection des opérandes se fait seulement dans le dernier segment, qui contient les instructions les plus anciennes. On économise ainsi beaucoup de comparateurs.
Certains chercheurs ont tenté de pipeliner l'étape de sélection des opérandes, ainsi que l'étage d'arbitrage. Mais en faisant cela, il faut plusieurs cycles pour détecter qu'une instruction a ses opérandes prêts, ce qui pose problème face à de nombreuses dépendances RAW. Pour éviter de trop perdre en performances, certains chercheurs ont décidé d'utiliser des techniques pour prédire quelles seront les instructions dont les opérandes seront bientôt prêts. Si détecter qu'une instruction est prête prend n cycles, le processeur devra tenter de prédire la future disponibilité des opérandes n cycles en avance pour obtenir des performances optimales.
===Remplacer la mémoire associative par une RAM===
Une autre optimisation possible est de remplacer la fenêtre d'instruction par une mémoire RAM, dont chaque mot mémoire correspondrait à une entrée. Une telle optimisation permet en théorie de se passer des comparateurs associés à chaque entrée, mais au prix de l'ajout de circuits annexes potentiellement couteux.
La première de ces techniques, la '''recherche directe par étiquette''' (direct tag search) fut créée par Weiss et son collègue Smith. Ils cherchaient à améliorer l'algorithme de Tomasulo, un algorithme qu'on expliquera dans quelques chapitres. Rappelons que chaque instruction produit un résultat, qui devra être rapatrié dans une ou plusieurs entrées de la fenêtre d'instruction. L'optimisation de la recherche directe par étiquette fonctionne dans le cas où le résultat de l'instruction n'est utilisé que par une seule entrée, et pas plusieurs.
Le principe est simple : chaque champ « opérande » d'une entrée est adressable via le nom de registre de cette opérande. Pour faire le lien entre entrée et nom de registre, la recherche directe par étiquette ajoute une table de correspondances matérielle, une mémoire RAM qui mémorise les adresses des champs « opérande » des entrées. Les adresses envoyées dans cette mémoire sont les noms de registres des résultats. Lors de l'émission, la table de correspondances est mise à jour. Les numéros des champs « opérande » réservés lors de l'émission sont mémorisés dans les mots mémoire qui correspondent aux deux registres sources. Toutefois, une petite vérification est faite lors de l'émission : si il y a déjà une correspondance dans la table pour un registre source, alors une autre instruction compte lire ce registre. Pour éviter d'écraser les données de l'instruction précédente dans la table de correspondances, l'émission de l'instruction est bloquée.
[[File:Recherche directe par étiquette.png|centre|vignette|upright=2|Recherche directe par étiquette.]]
==Les fenêtres d’instruction avec préplanification==
Avec la '''préplanification''' (''prescheduling''), la fenêtre d’instruction est composée de mémoires FIFO dans lesquelles les instructions sont triées dans l'ordre d'émission. Il n'y a pas de logique d’émission proprement dite, celle-ci se bornant à vérifier si l'instruction située au début de la FIFO peut s’exécuter. Par contre, le préplanificateur détecte les dépendances et en déduit où insérer les instructions dans le tampon d'émission, à l'endroit le plus adéquat.
[[File:Préplanification.jpg|centre|vignette|upright=2|Préplanification.]]
===L'usage de FIFO multiples===
La technique de préplanification la plus simple utilise plusieurs FIFO. Quand une instruction a une dépendance avec une autre instruction, les deux sont placées dans la même FIFO. Pour cela, le préplanificateur détecte les chaines d'instructions dépendantes, et place les instructions dans la FIFO adéquate.
A chaque cycle, l'unité d'émission lit une micro-opération dans chaque FIFO et vérifie si elle peut l'émettre. Si la micro-opération n'a pas ses opérandes disponibles, ou qu'une dépendance structurelle survient, alors la FIFO est bloquée. La micro-opération attend que la dépendance soit résolue, bloquant toutes les instructions précédentes. Mais les autres FIFO ne sont pas bloquées, laissant de la marge de manœuvre.
[[File:Préplanification par FIFO multiples.png|centre|vignette|upright=1.5|Préplanification par FIFO multiples.]]
===La technique du tampon trié===
La technique du '''tampon trié''' trie les instructions selon le temps d'attente avant leur exécution. Les instructions qui sont censées s’exécuter bientôt sont insérées au tout début de la FIFO, tandis que les instructions qui s’exécuteront dans un long moment sont insérées vers la fin. Là encore, l'unité d'émission lit une micro-opération dans la FIFO, et détermine si elle peut être émise. Si une dépendance survient, l'émission est retardée, et la FIFO est bloquée.
[[File:Préplanification par tampon trié.png|centre|vignette|upright=1.5|Préplanification par tampon trié.]]
La technique a cependant quelques problèmes assez importants. Le premier problème est que la FIFO est bloquée lorsqu'une micro-opération ne peut être émise. Le second problème est que le temps avant exécution n'est pas toujours connu, notamment pour les instructions d'accès mémoire. Et cela se répercute sur les instructions dépendantes de celles-ci. Pour résoudre ce genre de problèmes, l'usage d'un pipeline à ''replay'' est possible, et est même l'une des meilleures solution possible, malgré ses défauts.
L'idée est que les lecture sont pré-planifiées en supposant qu'elles font un succès de cache L1. Si la prédiction est fausse, la lecture est ré-exécutée plusieurs cycles plus tard, en supposant qu'elle fait un succès dans le cache L2, et ainsi de suite. Elle est alors ré-insérée dans la FIFO, elle est pré-planifiée une seconde fois. Il en est de même si une micro-opération ne peut pas être émise : il suffit de la réinsérer dans la FIFO au bon endroit, en attendant que ses dépendances soient résolus. Ainsi, l'instruction fautive ne bloque pas la fenêtre d’instruction, et retente sa chance autant de fois qu'il le faut.
[[File:Préplanification par fenêtre d’instruction scorebarodée.png|centre|vignette|upright=1.5|Préplanification par fenêtre d’instruction scorebarodée.]]
Une autre solution utilise deux fenêtres d’instruction : une FIFO gérée par la préplanification, et une vraie fenêtre d'instruction pour les instructions dont le temps d'attente ne peut pas être déterminé. Il est possible de mettre cette dernière avant la préplanification, afin de gérer les temps d'attente des instructions d'accès mémoire. Quand le temps d'attente d'une lecture devient connu, ses instructions dépendantes sont gérées avec préplanification.
[[File:Préplanification avec fenêtre d’instruction.png|centre|vignette|upright=2.5|Préplanification avec fenêtre d’instruction.]]
===Les fenêtres d'instruction avec allocation temporelle statique===
L''''allocation temporelle statique''' correspond à une technique utilisée sur les processeurs Cuzco de l'entreprise Condor. La technique en question a été présenté en 2025, mais des idées similaires ont été présentées dans la littérature académique. Elle ressemble beaucoup aux techniques de pré-planification, avec cependant quelques différences.
L'idée est simple : le processeur dispose de plusieurs fenêtres d'instruction, qui mettent en attente les micro-opérations. Lors de l'étape de renommage de registres, le processeur détermine dans combien de cycles d'horloge la micro-opération sera prête pour exécution. La micro-opération est alors mise en attente durant ce nombre de cycles, dans les fenêtres d'instruction, puis est émise une fois ce nombre de cycles écoulés. Ce faisant, les fenêtres d'instruction n'ont pas besoin de détecter la disponibilité des opérandes à chaque cycle, pour chaque micro-opération en attente. Le ''timing'' de l'émission des micro-opérations est décidé à l'avance.
L'implémentation exacte n'est pas connue, mais plusieurs solutions sont imaginables. Par exemple, la fenêtre d'instruction peut simplement être composée de plusieurs mémoires de petite taille, chacune contenant les micro-opérations destinées à être exécutées dans N cycles, N étant différent pour chaque mémoire. Une autre solution, plus réaliste, utilise une fenêtre d'instruction dans laquelle la micro-opération est couplée à des champs pour les opérandes, et un champ compteur. Le champ compteur est initialisé avec le nombre de cycles à attendre, et est décrémenté à chaque cycle d'horloge. Quand il atteint zéro, la micro-opération est émise.
Vous remarquerez que la méthode ne marche que si toutes les micro-opérations prennent un nombre fixe de cycles d'horloge. Et autant c'est le cas pour les micro-opérations arithmétiques ou logiques, autant les accès mémoire ne rentrent pas dans ce cadre. En théorie, on ne sait pas combien de temps prendra un accès mémoire. Et cette incertitude se répercute sur les instructions dépendantes d'un accès mémoire.
Pour éviter cela, le processeur réutilise un pipeline à ''replay'' tel que vu dans le chapitre précédent. L'unité de renommage de registre suppose que tout accès mémoire fait un succès de cache L1, et décide des ''timings'' d'émission sous cette hypothèse. Si une lecture subit un défaut de cache, elle est ré-exécutée, de même que toutes les instructions dépendantes. Pour cela, le registre de destination de la lecture est marqué comme invalide, grâce à un bit spécifique attaché au registre. Toute instruction qui lit une opérande invalide est elle aussi ré-executée de zéro.
Pour simplifier, l'unité d'émission ressemble à une unité d'émission dans l'ordre, avec un registre de disponibilité, qu'on aurait amélioré pour aller au-delà de la simple détection des dépendances d'instruction. Pour déterminer les ''timings'' d'émission, à savoir quand émettre une micro-opération, le processeur utilise une unité d'émission à deux étages. Le premier étage est appelé le ''register scoreboard'', le second étage est appelé la ''Time Ressource Matrix''. Les deux étages communiquent entre eux, comme on va le voir, et leurs noms donnent une idée de ce qu'ils font. Le premier étage gère les dépendances de registres, le second gère les dépendances structurelles.
Le premier étage est un registre de disponibilité amélioré, qui gère les dépendances de registre. Cet étage sait quand tel registre sera écrit, et donc que l'opérande écrite dedans sera disponible à partir de tel cycle d'horloge. Il le sait car l'étage suivant l'aura prévenu, mais laissons cela de côté pour le moment. Quand une micro-opération rentre dans cet étage, il vérifie les dépendances avec les registres et les micro-opérations antérieures. Vu qu'il sait quand les registres seront disponibles, il peut déterminer quand l'instruction pourra s'exécuter. Par exemple, si une micro-opération lit les deux registres R7 et R15, qui sont disponibles respectivement dans 5 et 7 cycles, le premier étage sait que la micro-opération devra attendre 7 cycles.
Mais tout cela ne suffit pas à déterminer quand émettre une micro-opération. En effet, il fait aussi gérer les dépendances structurelles, ce qui est le boulot du second étage. Le second étage utilise pour cela une ''Time Ressource Matrix'', qui mémorise l'occupation de diverses "ressources", pour les 256 cycles d'horloge à venir. Les ressources en question sont les ports de lecture/écriture du banc de registre, les ALUs utilisées, si la micro-opération utilise l'unité mémoire, etc. Le second étage prend en entrée une micro-opération, détermine quelles "ressources" elle utilise, puis regarde leur disponibilité. Elle détecte alors les dépendances structurelles et détermine alors quand peut s'exécuter la micro-opération. Elle peut retarder l'émission de quelques cycles, si une dépendance structurelle est détectée.
Une fois sortie du second étage, on sait quand la micro-opération va être émise, à quel cycle précisément. Et vu que la durée de l'instruction est fixe, on sait quand cette micro-opération va se terminer, quand son résultat sera disponible. Cela permet de mettre à jour la ''Time Ressource Matrix'', pour préciser que le port d'écriture sera occupé à tel moment. De plus, cette information est transmise au premier étage, qui sait alors quand est enregistré le résultat dans le registre de destination. C'est comme cela que le premier étage sait quand un registre est disponible : le second étage le prévient, quand il émet une instruction !
La ''Time Ressource Matrix''mémorise l'occupation des "ressources" pour les 256 cycles d'horloge à venir. Cependant, elle ne vérifie pas les dépendances pour les 256 cycles suivants. Quand elle reçoit une micro-opération, elle teste les dépendances structurelles pour seulement les 8 prochains cycles maximum. Si la micro-opération ne peut pas être émise pendant ces 8 cycles, le pipeline est bloqué, un ''pipeline stall'' est émis. Pour être plus précis, vu que le processeur peut décoder 8 micro-opérations en même temps, cela permet de ne consulter que 64 cycles d'horloge sur les 256.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les premiers processeurs Intel
| prevText=Les premiers processeurs Intel
| next=Le renommage de registres
| nextText=Le renommage de registres
}}
</noinclude>
1sptlekeqkf4d7xsycmywpqhwcx6eqc
773348
773347
2026-09-27T20:27:27Z
Mewtow
31375
/* Les fenêtres d'instruction décentralisées */
773348
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons parlé des techniques d'émission dans l'ordre. L'idée est d’exécuter une nouvelle instruction à chaque cycle, dans des unités de calcul séparées. Les instructions sont émises consécutivement et l'unité d'émission bloque l'émission en cas de dépendance, mais les instructions s’exécutent en parallèle dans des unités de calcul séparées. De plus, elles peuvent se finir dans le désordre, en absence de dépendances, si les exceptions sont imprécises. Le point important est qu'on doit exécuter des instructions indépendantes dans des unités de calcul séparées.
Les techniques d'exécution dans le désordre que nous allons voir dans ce chapitre sont dites à '''émission dans le désordre'''. Elles se distinguent des précédentes sur un point précis : l'unité d'émission bloque l'émission d'une instruction, elle seule est bloquée, les instructions suivantes peuvent poursuivre. L'émission des instructions peut donc se faire dans le désordre, dans le sens où une instruction peut être émise alors qu'une instruction précédente ne l'est pas encore.
L'avantage de l'émission dans le désordre est qu'une instruction s'exécute dès que ses opérandes sont disponibles. Le processeur incorpore de quoi déterminer quand un résultat est disponible, de quoi savoir quand un opérande est prête. Par prête, on veut dire présente soit dans le réseau de contournement, soit enregistrée dans les registres. Dès qu'une instruction a ses opérandes disponibles, elle est émise et exécutée. C'est là la seule contrainte : les dépendances WAR, WAW et autres sont gérées par un ROB ou toute autre technique d'exception précise, seules les dépendances RAW limitent l'exécution.
: Dans la suite, nous utiliserons parfois l'abréviation OOO pour parler d'exécution dans le désordre. L'abréviation est celle de ''Out Of Order Excution''.
Mais avant de poursuivre, précisons une chose importante : l'exécution dans le désordre traite les accès mémoire à part. La majorité des processeurs OOO assez anciens exécutent les accès mémoire dans l'ordre, seules les instructions entières/flottantes/branchements sont exécutées dans le désordre. L'exécution dans le désordre des accès mémoire est courante sur les processeurs récents, mais c'est l'unité d'accès mémoire qui se charge de changer l'ordre des accès mémoire toute seule dans son coin. Nous allons volontairement mettre de côté la gestion des accès mémoire dans ce chapitre.
==Les fenêtres d’instruction : généralités==
Pour implémenter l'exécution dans le désordre, il faut ajouter une sorte de mémoire qui met en attente les instructions bloquées, à émettre. Les instructions attendent dans cette mémoire le temps que leurs opérandes soient disponibles. De plus, il faut un circuit capable de gérer les dépendances RAW. L'unité d'émission qui bloquait les instructions, ne bloque plus rien, car elle faisait office de dépendance structurelle qui est éliminée.
[[File:File de micro-opération.png|vignette|upright=1|File de micro-opérations]]
Les processeurs sans exécution dans le désordre ont une unité d'émission qui bloque tout le pipeline dès qu'une instruction ne peut pas être émise. Pour limiter l'impact de ce blocage, quelques rares processeurs incorporent une mémoires FIFO appelée la '''file d'instruction''', aussi appelée '''file de micro-opération'''. Elles permettent d'émettre des instructions dans l'ordre, à savoir qu'elles quittent la file d'instruction dans l'ordre d'ajout, dans l'ordre de décodage. Le schéma ci-dessous illustre une telle file d'attente couplée à un ''scoreboard''.
Les techniques modernes d'OOO utilisent aussi des files de micro-opération améliorées, comme on le verra plus bas. De telles files de micro-opération permettent d'émettre des instructions dans le désordre, ce qui corrige le problème mentionné plus haut avec le ''scoreboard''. Si une instruction bloque le pipeline, les instructions suivantes peuvent être émises.
Il existe dans les grandes lignes 4 grandes techniques d'exécution dans le désordre. Elles peuvent se classer sur deux critères : est-ce que les instructions sont émises dans l'ordre, et combien il y a de files de micro-opération.
{|class="wikitable"
|-
!
! Emission dans l'ordre
! Emission dans le désordre
|-
! Une file de micro-opération
| ''Scoreboard''
| Fenêtre d'instruction centralisée
|-
! Plusieurs files de micro-opération
| Plusieurs FIFOs, plusieurs ''Scoreboard'' indépendants
| Fenêtre d'instruction décentralisée
|}
===L'usage de plusieurs files d'instruction===
La première méthode évoluée d'OOO utilise plusieurs files de micro-opération. L'idée est qu'une fois décodée, les instructions sont accumulées dans des files de micro-opération différentes. Il y a typiquement une file de micro-opération pour les opérations entières, une autre pour les opérations flottantes, une autre pour les accès mémoire. D'autres processeurs utilisent une file pour les accès mémoire et une autre pour les autres instructions, comme le faisait le Pentium 4 d'Intel.
Les files de micro-opération sont des mémoires FIFO, ce qui fait qu'elles conservent l'ordre des instructions. Et chaque FIFO est couplée à un ''scoreboard'' qui vérifie les dépendances à chaque cycle. La présence de ''scoreboard'' nous dit que l'intérêt n'est pas un mécanisme d'émission plus compliqué, mais que la technique se repose sur le fait d'avoir plusieurs files de micro-opération. L'intérêt d'avoir plusieurs FIFOs est que si une dépendance bloque une FIFO, les autres peuvent continuer d'émettre des instructions.
Les files de micro-opération émettent leurs instructions de manière indépendante, ou presque. Elles ne savent pas si telle instruction dans une autre file doit passer avant ou après l'instruction qu'elles émettent. Par contre, les unités d'émission communiquent entre eux pour gérer la disponibilité des registres. Quand un registre est lu ou écrit par une instruction, les autres files d'instruction doivent être prévenues et mettre à jour leurs unités d'émission. La logique d'émission est donc plus complexe que prévu. Le processeur Pentium 4 avait deux FIFOs séparées : une pour les instructions d'accès mémoire et une autre pour les autres instructions. Si jamais une instruction mémoire bloquait le pipeline, l'autre FIFO pouvait continuer à exécuter des instructions entières indépendante de la lecture bloquée.
Les FIFOs sont simples à implémenter et ont un cout en circuit modéré. Par contre, le gain en performances est assez limité avec cette méthode, surtout comparé aux méthodes qui vont suivre. Le problème est qu'il est facile de se retrouver avec toutes les files de micro-opération bloquées. Quand l'une est bloquée, les autres tendent à se vider rapidement, particulièrement quand c'est la file pour les accès mémoire qui se bloque. Les techniques qui vont suivre font totalement disparaitre ce genre de blocage.
===La fenêtre d'instruction centralisée===
Les processeurs OOO modernes utilisent une file d'attente modifiée, où les instructions sont insérées dans l'ordre, mais peuvent sortir dans le désordre, être émises dans le désordre. La file d'attente en question est appelée la '''fenêtre d'instruction'''. Les instructions décodées sont accumulées dans la fenêtre d'instruction, où elles attendent que leurs opérandes soient disponibles. La fenêtre d'instruction sert donc de file d'attente. A chaque cycle, l'unité d'émission consulte la fenêtre d'instruction pour voir quelles instructions peuvent s'exécuter, quelles instructions ont leur opérandes de disponibles. Elle choisit une instruction exécutable et l'envoie aux ALU. La fenêtre d'instruction doit spécialement être conçue pour, c'est une sorte de mémoire associative très complexe.
[[File:OOO Issue.png|centre|vignette|upright=2.5|Exécution dans le désordre avec une fenêtre d'instruction]]
Le cas le plus simple n'utilise qu'une seule fenêtre d'instruction. Avec elle, l'unité d'émission, détecte les dépendances et répartit les instructions sur les unités de calcul. Les instructions décodées sont ajoutées en parallèle dans la fenêtre d'instruction, et le ROB. La raison est que les instructions quittent la fenêtre d'instruction dans le désordre, l'ordre des instructions est perdu après l'unité de décodage/renommage de registre. Aussi, pour remplir le ROB, la seule opportunité est en sortie de l'unité de décodage. Dans le cas où une instruction est décodée en plusieurs micro-opérations, elles sont toutes insérées en même temps dans la fenêtre d'instruction et le ROB.
[[File:Fenêtre d'instruction.png|centre|vignette|upright=2|Fenêtre d'instruction.]]
Les micro-opérations mémoire sont à part des autres, pour diverses raisons. Une de ces raisons est que les dépendances de données sont classées en deux types : les dépendances de registre et les dépendances d'adresse. Et les deux sont fondamentalement différentes. Autant on peut détecter les dépendances de registre lors de l'émission, autant c'est plus compliqué avec les dépendances d'adresse. De plus, rappelons que si les lectures peuvent s'exécuter dans le désordre, les écritures doivent s'exécuter dans l'ordre, ou du moins en donner l'illusion. Pour cela, le processeur remet les écritures dans l'ordre avec une sorte de mini-ROB intégré à l'unité mémoire, appelée la file d'écriture. Elle complémente le ROB, qui lui ne gére que les écritures dans les registres, mais les deux collaborent.
Les processeurs grand public anciens n'autorisaient pas d'exécution dans le désordre des accès mémoire. Pour cela, l'unité mémoire est précédée par une file de micro-opérations, pour forcer l'exécution dans l'ordre des accès mémoire. Les processeurs modernes ajoutent des techniques d'exécution dans le désordre des accès mémoire, mais nous les verrons dans un chapitre dédié. Dans les deux cas, les micro-opérations mémoire doivent être envoyées à l'unité mémoire dans l'ordre du programme, dans l'ordre de décodage. Dans ce chapitre, on suppose que l'unité mémoire est précédée d'une file de micro-opération mémoire dédiée, rien que pour elle. Elle est à part du reste.
[[File:Processeur avec émission dans l'ordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans l'ordre des accès mémoire]]
===Les fenêtres d'instruction décentralisées===
Une autre solution utilise plusieurs fenêtres d'instruction. Pour faire la différence avec l'usage d'une fenêtre d'instruction unique, nous allons parler de fenêtre d'instruction centralisée s'il n'y en a qu'une dans le processeur, de '''fenêtre d'instruction décentralisée''' s'il y en a plusieurs. Dans le cas général, chaque fenêtre d'instruction est associée à un type précis d'opération/instruction. La tendance moderne est d'utiliser une fenêtre d'instruction pour les instructions de calcul entières, une autre pour les flottantes. Il s'agit d'un choix simple et efficace, mais quelques processeurs font autrement, pour répondre à des contraintes très diverses.
L'usage de fenêtres décentralisées simplifie la répartition des instructions sur les différentes unités de calcul. Par exemple, utiliser des fenêtres séparées pour les ALU et les FPU facilite la répartition des instructions de calcul sur les ALU. Une fenêtre centralisée demande de faire la différence entre instruction entière/flottante, puis de choisir sur quelle unité de calcul entière/flottante utiliser. Une fenêtre décentralisée fait les deux choix séparément : d'abord on envoie l'instruction dans la bonne fenêtre suivant si c'est une instruction flottante ou entière, et ensuite on gère la disponibilité des ALUs/opérandes. Et on peut adapter la même idée en séparant les unités d'accès mémoire des ALU entières, etc.
Par contre, un défaut est que certaines fenêtres d'instruction peuvent être sous-utilisées. Par exemple, la station de réservation flottante est inutilisée si le programme n'exécute pas d'opérations flottantes. Ou encore, les fenêtres pour les accès mémoire peuvent être sous-utilisées, si le programme effectue peu d'accès mémoire relativement aux autres instructions. En fait, tout dépend de comment sont calibrées les fenêtres d'instruction et de la répartition des instructions dans le programme entre opérations entières, flottantes, mémoire. Par exemple, si un programma de deux fenêtres d'instructions identiques, une pour les calculs entiers et une autre pour les calculs flottants, elles ne seront utilisées à la perfection que si la moitié des instructions du programme fait des calculs flottants et l'autre des calculs entiers. Toute déviation par rapport à cette répartition idéale risque de laisser des entrées vides dans une fenêtre d’instruction.
Un point important est que l'émission est découpée en deux étages avec des fenêtres d'instruction décentralisées. La première étape envoie l'instruction décodée vers la fenêtre d'instruction adéquate, la seconde gère l'émission proprement dit. La première est l'étage de ''dispatch'' qui envoie l'instruction dans la fenêtre d’instruction adéquate, selon que c'est une instruction flottante, entière, autre. La seconde est l'étage de ''scheduling'', qui gère la disponibilité des opérandes et des unités de calcul. En clair, on trie les instructions suivant leur type, suivant le type d'unité de calcul adéquate, puis on l'émet proprement dit. Faire ainsi a plusieurs avantages, avec cependant peu d'inconvénients.
Lors de l'étape de ''dispatch'', l'instruction émise est ajoutée au tampon de réordonnancement, s'il existe. Sans cela, impossible de conserver l'ordre des instructions. Les instructions sont émises dans l'ordre au niveau de l'unité de ''dispatch'', pas au niveau de l'étage de ''scheduling''. Aussi, pour remplir le ROB, cela doit se faire au dernier moment où les instructions sont émises dans l'ordre, soit en sortie de l'unité de ''dispatch''. De plus, les micro-opérations mémoire sont traitées à part des autres, elles sont envoyées directement à l'unité mémoire. La gestion des calculs d'adresse est quelque peu complexe, mais ils peuvent être fait soit dans l'unité mémoire, soit dans les ALU entières, peu importe. Il y a aussi un système de contournement complexe entre ALU/FPU et unité mémoire, qui n'est pas représenté dans le schéma ci-dessous.
[[File:Processeur avec plusieurs fenêtres d'instruction.png|centre|vignette|upright=2.5|Processeur avec plusieurs fenêtres d'instruction.]]
===Un cas extrême : les fenêtres d'instruction totalement décentralisées===
Prenons un cas extrême de fenêtre d'instruction décentralisée : celui où chaque ALU entière a sa propre station de réservation. En clair : une fenêtre d'instruction par ALU/FPU ! On parle de '''fenêtres d'instruction totalement décentralisées'''. Vous vous dites que ce cas est un peu trop extrême pour être réaliste, mais il n'en est rien. C'est même très courant sur les architectures basse consommation, et sur pas mal de CPU x86 modernes.
Un cas aussi extrême entraine une légère perte de performance, comparé à une fenêtre d'instruction centralisée ou partiellement décentralisée. Mais elle entraine aussi un gain considérable en termes de consommation énergétique. Et c'est la raison pour laquelle les fenêtres d'instructions totalement décentralisées sont utilisées sur les architectures basse consommation. Voyons pourquoi.
La perte de performance a plusieurs raisons. Mais la principale est que
Ou encore, une station de réservation peut avoir plusieurs µops prêtes, et une autre zéro, ce qui fait qu'une seule. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
===Avantages et inconvénients de chaque implémentation===
Un processeur contient soit une fenêtre d'instruction unique, soit plusieurs fenêtres d'instruction séparées. Typiquement, les fenêtres d’instruction séparées sont spécialisées, dans le sens où on a une fenêtre pour les instructions flottantes, une autre pour les instructions entières, une autre pour les accès mémoire, etc. Pour résumer, on a le choix entre une grosse fenêtre généraliste et plusieurs fenêtres spécialisées, sauf que la fenêtre centralisée est lente et complexe, contrairement aux fenêtres spécialisées. Les deux méthodes ont des inconvénients et des avantages différents.
Dans les grandes lignes, l'avantage des fenêtres décentralisées est qu'elles sont plus petites qu'une grosse fenêtre d'instruction. Cela simplifie la gestion du contournement et de la répartition des instructions sur les unités de calcul, comme on le verra dans quelques paragraphes. Elles sont donc moins complexes et plus rapides. L'autre avantage, c'est qu'il est possible de démarrer l'exécution de plusieurs instructions simultanément : une instruction par fenêtre décentralisée, contre une par fenêtre d'instruction.
Mais les fenêtres décentralisées sont souvent sous-utilisées, partiellement remplies, contrairement aux fenêtres d'instruction. Il arrive qu'une fenêtre d'instruction soit remplie, alors que les autres sont vides. Par exemple, prenons un processeur avec une fenêtre d'instruction reliée à la FPU, et une autre reliée aux autres ALUs. Si le processeur n'exécute que des opérations flottantes, la fenêtre reliée à la FPU sera pleine, alors que l'autre sera vide. L'exécution des instructions dans le désordre est alors limitée par la petite taille de la fenêtre, qui ne peut plus accepter de nouvelle instruction flottante. Avec une fenêtre unique, on n'aurait pas eu ce problème : on aurait eu une énorme fenêtre d'instruction remplie d'instruction flottante, au lieu d'une petite fenêtre spécialisée.
Il est maintenant temps de voir en quoi sont faites les fenêtres d’instruction, qu'elles soient centralisées ou décentralisées. Nous allons aussi voir quelle est la logique d'émission associée.
==L'implémentation d'une fenêtre d'instruction : généralités==
Une fenêtre d'instruction est une mémoire qui mémorise des micro-opérations. Elle est cependant plus complexe qu'une mémoire RAM ou qu'une FIFO et ressemble un petit peu à une mémoire cache. Elle dispose d'un port d'écriture et d'un port de lecture, au minimum.
Le port d'écriture permet à l'unité de décodage d'insérer une micro-opération dedans, d'ajouter la micro-opération qui vient d'être décodée. Si une instruction machine est décodée en plusieurs micro-opération, deux possibilités. La première est de les insérer un par un, ce qui fait qu'on peut se contenter d'un seul port d'écriture. L'autre possibilité est de les ajouter en même temps, ce qui demande plusieurs ports d'écriture.
Le port de lecture sert à émettre une instruction. On part du principe que la fenêtre d'instruction ne peut émettre qu'une seule instruction à la fois, car les processeurs que nous avons vu précédemment sont de ce type. Mais nous verrons dans quelques chapitre qu'il existe des processeurs dits superscalaires, qui sont capables d'émettre plusieurs instructions par cycle. Dans ce cas, la fenêtre d'instruction a un seul port de lecture. Un processeur superscalaire, quant à lui, doit avoir un port de lecture par unité de calcul, ce qui fait beaucoup plus.
===Les entrées d'une fenêtre d’instruction===
Les fenêtres d'instruction sont composées d''''entrées''', des mots mémoire qui stockent une instruction. Une instruction réserve une entrée après son décodage et la libère dès qu'elle est envoyée aux unités de calcul. Une entrée contient l'opcode (les signaux de commande à envoyer à l'ALU), le registre de destination du résultat, les registres de chaque opérande, et éventuellement des signaux de commande en plus. De plus, chaque opérande est couplée à un bit de disponibilité, qui indique si elle est disponible. Quand un opérande est écrit dans les registres, le bit de présence correspondant est mis à jour. Enfin, chaque entrée possède un bit ''empty'' qui indique si elle est vide, cette information étant utile pour réserver des entrées.
[[File:Entrée d'une station de réservation - sans les tags.png|centre|vignette|upright=3|Entrée d'une fenêtre d'instruction]]
Nous verrons dans quelques chapitres que certaines fenêtres d'instruction sont capables de mémoriser les opérandes des instructions. De telles fenêtres d'instruction seront appelées, dans ce cours, des '''stations de réservation'''. Les entrées des stations de réservation sont assez semblables à celle des fenêtres d'instruction, à un détail près : les opérandes de l'instruction sont enregistrées directement dans des champs séparés. Il y a deux champs, un par opérande, pour stocker l'opérande elle-même, lue depuis les registres ou obtenue via le réseau de contournement. Notons que les champs pour les opérandes viennent en plus des champs pour les noms de registres, encore que les deux peuvent être fusionnés si on veut optimiser le tout.
[[File:Entrée d'une station de réservation.png|centre|vignette|upright=3|Entrée d'une station de réservation.]]
Précisons que la terminologie n'est pas très fiable. Les termes "fenêtre d'instruction" et "station de réservation" ne regroupent pas du tout la même chose d'un papier de recherche à l'autre, d'un bouquin à l'autre, d'un chercheur à l'autre, d'un ingénieur à l'autre, d'un professeur à l'autre. Le terme initial vient pourtant de l'article original sur l'algorithme de Tomasulo, où il désignait une fenêtre d'instruction associée à une unité de calcul précise. Mais le terme a ensuite été réutilisé, ce qui fait que certains processeurs avec plusieurs unités de calcul sont décrit comme ayant une unique station de réservation. En bref : c'est le bazar ! Mais nous en reparlerons dans le chapitre sur le renommage de registres, car c'est la place idéale pour les aborder.
Toujours est-il que j'ai fait le choix de confondre les concepts de fenêtre d'instruction et de station de réservation avec les concepts de '''lecture avant émission''' et de '''lecture après émission'''. La différence entre les deux est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation. Dans le cas "avant émission", les opérandes sont mémorisées dans la station de réservation. Et cette différence a une influence sur le pipeline du processeur, le banc de registres et tout ce qui s'en suit. Un désavantage de la lecture avant émission est qu'il faut stocker les opérandes après lecture, ici dans les stations de réservation. Mais un avantage est que le banc de registre a besoin de moins de ports de lecture, car une bonne partie des opérandes sont fournies par le système de contournement. Nous verrons cela dans le détail dans le chapitre sur les processeurs superscalaires.
Il a été proposé de fusionner le tampon de ré-ordonnancement avec la fenêtre d'instruction dans une structure unique. La méthode a notamment été utilisée sur les processeurs d'architecture K6 d'AMD. Le tout donnait une structure appelée le '''DRIS''' (''deferred scheduling register renaming instruction shelf''). La différence avec une fenêtre d’instruction normale est que les entrées sont libérée non pas quand l'instruction est exécutée/émise, mais quand elle termine son exécution et quitte le ROB. La fenêtre d'instruction doit alors incorporer des bits pour savoir si telle entrée a émis sont instruction ou non.
===La logique d'éveil et de sélection===
Dans le cas qui va suivre, on suppose que le processeur ne peut émettre qu'une instruction à la fois. Chaque cycle, une instruction est sélectionnée et est envoyée dans le chemin de données, soit à une ALU, soit à une unité d'accès mémoire. La sélection d'une instruction est effectuée par l'unité d'émission, qui est aussi appelée le '''''scheduler'''''. Elle gère la disponibilité des opérandes et la disponibilité des unités de calcul adéquate. Le choix de l'instruction émise doit être le plus pertinent possible, pour des raisons de performances.
Pour le premier point, l'unité d'émission détermine quelles instructions sont candidates pour l'émission. Les ''instructions candidates'' sont celles dont tous les opérandes sont prêts. Le processeur contient un circuit qui détecte les instructions candidates : la '''logique d’éveil''' (''wake-up logic''). Une fois que la logique d'éveil a fait son travail, une instruction est choisie pour s’exécuter en fonction de la disponibilité des unités de calcul adéquates. Ce rôle est dévolu à un circuit qu'on appelle la '''logique de sélection''', ou ''select logic''.
[[File:Fonctionnement complet de la logique d’émission.jpg|centre|vignette|upright=1.5|Fonctionnement complet de la logique d’émission.]]
La logique de sélection doit gérer le fait que les unités de calcul du processeur ne sont pas toutes identiques. Déjà, un processeur a généralement des unités séparées pour les instructions entières et flottantes, des unités spécialisées dans les accès mémoire, parfois des unités spécialisées pour les branchements. Une instruction doit être attribuée à la bonne unité de calcul, on ne doit pas envoyer une instruction entière dans une unité de calcul flottante. De plus, parmi les unités de calcul entières, toutes ne sont pas identiques. Par exemple, il est fréquent d'avoir plusieurs unités de calcul spécialisées dans les opérations simples (additions, soustractions, opérations logiques) avec une unité pour les multiplications/divisions séparées, parfois une unité spécialisée pour les décalages/rotations, etc. Là encore, allouer la bonne instruction à la bonne ALU est complexe. Plus les unités de calcul sont hétérogènes, plus la logique de sélection est compliquée.
Les logiques d'éveil comme de sélection sont assez complexes. Comme on le verra plus bas, la logique de sélection la plus simple, qui gère une seule unité de calcul, est basée sur un encodeur à priorité couplé à d'autres circuits. Et pour gérer plusieurs ALUs, il faut mettre des encodeurs en cascade. Le résultat est que même en prenant une logique de sélection simple, elle sera assez lente et gourmande en circuits. Aussi, la logique de sélection met souvent un cycle d'horloge entier pour faire son travail, elle a son propre étage de pipeline attribué. La logique d'éveil est dans le même cas, ce qui fait qu'émettre une micro-opération demande deux cycles d'horloge.
Le défaut est que cela rajoute des cycles d'horloges entre la fenêtre d'instruction et l'ALU, ce qui complique la gestion du pipeline. Ces cycles de retard sont à prendre en compte au moment d'émettre les instructions, les ''scheduler'' doit en tenir compte pour des performances optimales. Sans tenir compte de ces cycles de retard, les instructions arrivent en retard de quelques cycles à l'ALU, cycles de retard durant lesquels l'ALU n'a pas fait de calcul et a été sous-utilisée, sans compter que le système de contournement n'est pas utilisé au mieux. Et le problème est encore plus important sur les processeur avec des stations de réservation, qui utilisent la "lecture après émission" : cela rajoute un cycle d'horloge en plus, un temps de retard en plus.
===La logique d'éveil : l'émission anticipée===
La logique d'éveil doit tenir compte du fait qu'il y a plusieurs étages entre l'émission et l'exécution sur une unité de calcul. Par exemple, un processeur a souvent entre un et à cinq étages entre la file d'attente et les unités de calcul. Il faut lire les registres, gérer le contournement, et cela peut prendre quelques cycles d'horloge. Sur le Pentium 4, on trouve 6 étages entre la fenêtre d’instruction et l'entrée de l'ALU. Si les micro-opérations sont émises quand les opérandes sont disponibles, il y aura un délai de quelques cycles avant qu'elles atteignent l'ALU. Alors qu'idéalement, on souhaiterait que la micro-opération atteigne l'ALU dès que ses opérandes sont disponibles, pour profiter des techniques de contournement. Pour cela, la logique d'éveil émet les instructions quelques cycles en avance pour qu'elles arrivent au bon moment. Il s'agit d'une technique d''''émission anticipée''', dont nous avions parlé dans le chapitre sur le contournement.
: Il faut noter que le problème se pose plus avec des fenêtres d'instruction qu'avec des stations de réservation. Les premières ont un étage en plus entre émission et ALU, vu qu'il faut lire des registres après l'émission.
L'émission anticipée demande d'émettre un signal d'éveil en avance de quelques cycles, pile au bon moment. Par exemples, si on a deux étages entre l'ALU et l'unité d'émission, le signal de réveil sera généré deux cycles avant que le résultat soit effectivement calculé. L'implémentation varie, mais deux solutions sont possibles. La première est de générer le signal d'éveil dans l'ALU, ce qui n'a de sens que pour les opérations comme les multiplications ou les divisions, qui prennent plusieurs cycles. Une autre technique génère le signal de réveil dans une structure centralisée, spécialisée dans la génération des signaux d'éveil. L'unité de composée d'un registre à décalage par registre. Imaginons qu'une instruction est émise, que cette instruction mette N cycles à s'exécuter, et qu'elle écrive dans un registre de destination. L'unité sélectionne alors le registre à décalage associé au registre de destination, et met le bit numéro N à 1. Le registre décalé d'un cran à chaque cycle. Quand le bit sortant est un 1, alors le signal de validité pour l'opérande est généré.
L'émission anticipée marche bien si l'instruction a une durée fixe, connue à l'avance. Et autant c'est le cas pour les instructions arithmétiques et logiques, autant ce n'est pas le cas pour les accès mémoire. Les accès mémoire ont une latence variable : quelques cycles pour un succès de cache, plusieurs dizaines de cycles pour un défaut de cache L1, pas loin de la centaine pour un défaut de cache L2, etc. Et cela pose des problèmes pour l'émission anticipée.
L'émission en avance pour les accès mémoire est purement spéculative : le processeur émet des instructions en supposant que la lecture entrainera un succès de cache, quitte à annuler l'instruction en cas de défaut de cache. Mais faire ainsi demande non seulement d'annuler les instructions émises en avance, mais aussi de ne pas retirer les instructions émises en avance de la fenêtre d'instruction. Elles sont émises, mais une copie de sauvegarde est conservée dans la fenêtre d'instruction. Si le processeur détecte un défaut de cache, il peut alors ré-exécuter l'instruction. S'il détecte un succès de cache, les instructions lecture-dépendante sont retirées de la fenêtre d'instruction, la copie de sauvegarde est devenue inutile et est donc jetée.
===La logique de sélection : âge ou position ?===
La logique de sélection est primordiale pour de bonnes performances. Il existe deux méthodes principales pour sélectionner l'instruction à exécuter. La première est la plus simple : elle se base sur lé numéro de l'entrée. En effet, une fenêtre d'instruction est avant tout une sorte de mémoire, un mix entre mémoire associative, mémoire RAM, et FIFO. Et chaque entrée a un numéro, une adresse mémoire. Nous parlerons de numéro dans ce qui suit, car ce sera plus simple, le terme d'adresse mémoire étant peut-être un peu exagéré dans le contexte des fenêtres d'instruction.
Toujours est-il que la fenêtre d'instruction est adressable dans le sens où on peut lui envoyer le numéro de l'entrée voulue, et la fenêtre d’instruction renvoie le contenu de l'entrée sur son port de lecture. Et c'est ce que fait la logique de sélection : elle génère le numéro de l'entrée à lire. Le numéro est alors envoyé sur l'entrée d'adresse de la fenêtre d’instruction, et elle fournit la micro-opération associée sur son port de lecture, son '''port d'émission'''.
La première méthode de sélection, donc. Elle est basée sur sur le numéro de l'entrée. Si plusieurs entrées sont éveillées, la logique de sélection prend celle avec le plus petit numéro. Elle porte le nom de '''sélection par position''', sous-entendu position dans la fenêtre d'instruction. Vous l'avez peut-être deviné, mais la logique de sélection est alors un encodeur à priorité. Le circuit de sélection ne se résume donc pas seulement à un encodeur à priorité, mais c'est le circuit central. L'avantage d'une telle méthode de sélection est qu'elle est simple à implémenter : un encodeur à priorité est un circuit connu, facile à implémenter. Mais on remarque un défaut : un encodeur est un circuit assez rapide, mais qui utilise beaucoup de transistors, beaucoup de portes logiques, surtout si on veut qu'il soit rapide. Ce qui explique que la logique de sélection ait son propre étage de pipeline rien que pour elle.
La seconde méthode s'appelle la '''sélection par âge''', au nom assez transparent. L'idée est de privilégier l'émission de l'instruction la plus ancienne. Elle demande que la fenêtre d'instruction trie les micro-opérations par ordre d'insertion dans la fenêtre d'instruction, ce qui en fait une pseudo-FIFO. Le tri est partiel, car les instructions peuvent sortir de la fenêtre d’instruction à tout moment. Compacter la fenêtre d'instruction, à savoir regrouper toutes les entrées valides est une idée, mais elle est compliquée à implémenter. Aussi, elles préfèrent encoder des informations sur l'ordre d'émission dans chaque entrée. L'encodeur à priorité doit alors travailler non pas sur des nombres entiers liés à l'ordre d'insertion dans la pseudo-FIFO. Elle est encore plus gourmande en circuits que la méthode précédente.
Les processeurs haute performance utilisent souvent des méthodes hybrides. Par exemple, les processeurs AMD de microarchitecture Bulldozer utilisaient une méthode intermédiaire. Ils utilisaient une fenêtre d'instruction couplée à un encodeur à priorité pour la sélection par position, mais complémentaient le tout avec une unité qui mémorisait l'instruction la plus ancienne dans la fenêtre d’instruction. Le mécanisme de sélection par âge était utilisé en priorité. Mais si l'instruction la plus ancienne n'était pas prête, le processeur basculait sur le mécanisme de sélection par position.
===La compaction des fenêtres d'instruction===
À chaque cycle, les instructions décodées sont ajoutées dans la fenêtre d'instruction, dans des entrées vides. Vu que les instructions quittent celle-ci dans le désordre, ces vides sont dispersés dans la fenêtre d'instruction, ce qui pose problème pour déterminer où placer les nouvelles instructions. La solution la plus triviale consiste à conserver une liste des vides, mise à jour à chaque insertion ou émission d'instruction. Une autre solution consiste à éliminer les vides en compactant la fenêtre d'instruction à chaque cycle d'horloge. Des circuits se chargent de détecter les vides et de regrouper les instructions en un unique bloc. Il faut signaler que certaines processeurs arrivent à se passer de cette étape de compactage, mais au prix de fenêtres d'instruction nettement plus complexes.
Autre problème : quand il faut choisir quelle instruction émettre, il y a toujours plusieurs candidats. Si on choisit mal, des instructions restent en attente trop longtemps parce que d'autres instructions plus jeunes leur passent devant. Pour éviter cela, les instructions les plus vielles, les plus anciennes, sont prioritaires. Pour cela, on peut utiliser une FIFO un peu spéciale pour la fenêtre d'instruction. Si les ajouts d'instruction se font dans l'ordre, les instructions ne quittent pas forcément la fenêtre d'instruction dans l'ordre imposé par une FIFO : les instructions restent triées dans leur ordre d'ajout, même s'il y a des vides entre elles. Dans ces condition, il est préférable que le compactage conserve l'ordre FIFO des instructions. Dans ces conditions, l'instruction la plus ancienne est celle qui est située à l'adresse la plus faible : le circuit de sélection peut donc être fabriqué avec des encodeurs, et est relativement simple.
==Les fenêtres d'instruction basées sur une matrice de dépendances==
L'implémentation la plus simple étend le fonctionnement d'un pipeline dynamique usuel. Rappelez-vous le chapitre sur les pipeline dynamiques. Nous avions vu que de tels pipelines émettaient une instruction par cycle, quitte à émettre des bulles de pipeline en cas de dépendance bloquante. Et il est possible d'améliorer leur unité d'émission pour qu'elle soit compatible avec l'exécution dans le désordre.
===Rappels sur le registre de réservation/disponibilité===
L'émission d'une instruction est gouvernée par un ''registre de réservation'' qui indique quels registres sont réservés par une instruction, et ceux qui sont libres. Un registre réservé est un registre qui sera écrit par une instruction en cours d'exécution, mais qui n'a pas encore écrit son résultat. Les instructions candidates doivent avoir leurs opérandes dans des registres libres. Sinon, c'est signe que leurs opérandes sont réservées en écriture, donc pas encore écrites, donc pas disponibles. Si une instruction candidate veut lire ce registre, c'est signe qu'il y a une dépendance RAW : elle veut lire un registre réservé, qui est destiné à être écrit par une instruction en vol. Si elle veut écrire, c'est signe qu'elle veut écrire dans un registre où une autre instruction veut écrire : c'est une dépendance WAW. Dans les deux cas, l'instruction n'est pas émise.
Le registre de réservation encode les registres réservés comme suit : chaque bit est associé à un registre. Le bit numéro 0 est associé au registre numéro 0, le bit numéro 1 au registre numéro 1, etc. Là, En clair, les numéros de registres réservés sont encodés en représentation non pas binaire, mais en représentation ''one-hot'' (vue au premier chapitre).
{|class="wikitable"
|+ Registre de réservation
|-
! Registre 7 !! Registre 6 !! Registre 5 !! Registre 4 !! Registre 3 !! Registre 2 !! Registre 1 !! Registre 0
|-
| 0 || 0 || 1 || 0 || 0 || 1 || 0 || 0
|}
Avant d'émettre une instruction, l'unité d'émission extrait les registres opérande/destination et les convertis dans la même représentation que le registre de réservation, à savoir en représentation ''one-hot''. Le circuit de traduction binaire vers ''one-hot'' est, pour rappel, un simple décodeur. Puis, on fait un OU logique entre les sorties des décodeurs. Le résultat est un masque qui indique quels registres sont lus par l'instruction, encodé en représentation ''one-hot''. Appelons-le le '''masque d'opérandes'''. Le masque est alors comparé au registre de réservation pour vérifier si l'instruction peut être émise.
[[File:Unité d'émission simple, dans l'ordre.png|centre|vignette|upright=2|Unité d'émission simple, dans l'ordre]]
Si l'émission est autorisée, le registres de réservation est là aussi mis à jour. Le registre de réservation est aussi mis à jour dès qu'une instruction enregistre son résultat dans les registres, ou alors dès qu'il est disponible pour le contournement. Le bit associé au registre repasse alors à 0 (ou à 1). Sans contournement, la mise à jour se fait alors à la toute fin de l'instruction, quand elle se termine, lors de la dernière étape d'enregistrement, à la fin de son pipeline. Avec, elle se fait quand l'instruction quitte l'unité de calcul.
===L'extension à l'exécution dans le désordre : l'éveil par matrice de bits===
La différence entre un pipeline dynamique et l'exécution dans le désordre est que l'on passe d'une instruction à émettre à plusieurs. Plusieurs instructions sont mises en attente dans une fenêtre d’instruction, et y attendent leur tour. L'idée est alors d'envoyer le registre de disponibilité à toutes les entrées, à toutes les instructions en attente. Ainsi, on sait quelles sont les instructions qui peuvent s'exécuter et celles qui ne le peuvent pas. L'implémentation est assez simple, elle ressemble beaucoup à l'implémentation d'un pipeline dynamique simple, si ce n'est que des circuits sont dupliqués.
L'implémentation utilise des fenêtres d'instruction légèrement différentes de celles introduites plus haut. Elles n'utilisent notamment pas de bits de disponibilité, mais autre chose. Lorsqu'une instruction est ajoutée à la fenêtre d'instruction, le masque de disponibilité des opérandes est stocké dans la fenêtre d'instruction. A chaque cycle, le registre de disponibilité est envoyée à toutes les entrées, et est comparé avec tous les masques de disponibilité des opérandes. Si une comparaison renvoie un 1, alors l'instruction de l'entrée est une instruction candidate à l'émission.
[[File:Unité d'émission dans le désordre basée sur un registre de disponibilité.png|centre|vignette|upright=2.5|Unité d'émission dans le désordre basée sur un registre de disponibilité]]
L'ensemble est implémenté avec l'aide d'une '''matrice de bits''', où chaque ligne correspond à une entrée et chaque colonne à un registre. À chaque croisement entre une ligne et une colonne, on trouve un bit. Si le bit de la ligne n et de la colonne m est à 1, cela veut dire : l'instruction dans l'entrée n a besoin de lire le registre m. S’il est à 0, alors cela veut dire que l'instruction stockée dans la ligne n n'a pas besoin de la donnée dans le registre m.
[[File:Planificateur à matrice de bits.jpg|centre|vignette|upright=2|Planificateur à matrice de bits.]]
Lorsqu'une instruction réserve une entrée, elle initialise la ligne en fonction des registres qu'elle souhaite lire. Pour vérifier si une instruction a ses opérandes prêts, le processeur compare le registre de disponibilité à la ligne associée à l'instruction/entrée. A chaque intersection ligne-colonne, se trouve un comparateur de 1 bit, qui détecte si le registre est demandé et disponible. Les résultats de cette porte sont ensuite envoyés à une porte ET, qui fait le gros du travail.
[[File:Logique de détection de la disponibilité d'une instruction.jpg|centre|vignette|upright=2|Logique de détection de la disponibilité d'une instruction.]]
==Les fenêtres d'instruction basées sur une mémoire associative==
Les processeurs modernes utilisent une mémoire associative pour la fenêtre d'instruction. Les mémoires associatives ont été abordées il y a quelques chapitres, mais faisons quelques rappels. Les mémoires associatives servent à accélérer la recherche de données dans un ensemble. Or, une fenêtre d'instruction vérifie régulièrement si chaque entrée est prête. Il s'agit d'un processus de recherche d'une instruction qui respecte une condition bien précise dans la mémoire, ce qui fait que les mémoires associatives sont donc tout indiquées.
Comme vous le savez, les signaux de commande d'une instruction sont propagés avec celle-ci dans le pipeline, le nom du registre de destination ne faisant pas exception. Le nom de registre est envoyé à la fenêtre d'instruction lors de l'écriture du résultat dans les registres. S'il y a correspondance, l'opérande est disponible et son bit de disponibilité est mis à 1. On peut adapter cette méthode pour tenir compte du contournement assez simplement.
[[File:Détection des dépendances par propagation du registre de destination.png|centre|vignette|upright=2|Détection des dépendances par propagation du registre de destination.]]
En conséquence, le circuit de détection des dépendances est constitué d'un grand nombre de comparateurs : un par champ « nom de registre » dans chaque entrée. La logique d'éveil/''wake up'' regroupe tous les comparateurs, la logique de sélection est un circuit situé en-dehors de la mémoire associative. Les entrées sont dans la mémoire associative elle-même.
[[File:Gestion des bits de validité.png|centre|vignette|upright=2|Gestion des bits de validité.]]
L'implémentation la plus simple est celle du schéma précédent, où on utilise une mémoire associative unique. Une version plus élaborée sépare la fenêtre d'instruction en deux parties : une mémoire qui mémorise les registres des opérandes, une autre mémoire pour le reste. La mémoire pour les registres opérandes est la mémoire associative proprement dite, c'est elle qui est associées aux comparateurs, à la logique de ''wake-up''/réveil, etc. Par contre, l'autre mémoire est une mémoire RAM simplifiée, qui mémorise les informations qui n'ont pas besoin des comparateurs. L'opcode de l'instruction est là-dedans, par exemple.
===Les optimisations de la consommation d'énergie : la désactivation des portions inutilisées===
Le problème avec des fenêtres d'instruction de ce type est leur forte consommation énergétique, ainsi que le grand nombre de portes logiques utilisées, qui deviennent prohibitif pour des fenêtres d'instruction un peu grosses. Pour résoudre ce problème, certains ont optimisé les comparateurs. D'autres ont tenté de profiter du fait que la majorité des bits dans les entrées sont composés de zéros. Bref, les optimisations purement matérielles sont légion.
Une première solution est de désactiver les entrées qui sont inutilisées, vides, sans instruction. Pour cela, on fait précéder les comparateurs par un circuit qui les connecte ou déconnecte des lignes de bit. Le circuit est commandé par le bit ''empty'' qui indique si une entrée est vide ou non. L'avantage est que la consommation d'énergie de la fenêtre d'instruction devient alors proportionnelle au nombre d'entrées non-vides, et non au nombre d'entrées total. Du moins, en apparence, les entrées non-utilisées doivent quand même être alimentées pour fonctionner, mais l'activité de comparaison disparait dans les entrées vides. Il est possible d'étendre cette technique aux entrées dont les opérandes sont prêtes. Les gains sont généralement assez bons, avec une réduction de consommation variant entre 30 et 50%, avec une implémentation très simple et très économe en circuits.
Une autre solution, assez simple à mettre en place, consiste à désactiver une partie de la fenêtre d'instruction sin elle est inutilisée. Pour cela, il faut segmenter la fenêtre d'instruction avec la technique du ''wire partitionning''. Pour rappel, une fenêtre d'instruction est une mémoire associative. Et comme toute mémoire associative, elle contient des fils, les lignes de bit, qui relient les entrées/sorties à toutes les cellules mémoire. Plus une ligne de bit est longue, plus elle consomme d'énergie et plus le temps de lecture/écriture est long.
L'idée est de segmenter les lignes de bits en plaçant des répéteurs qui recopient la tension d'un segment au suivant. Les répéteurs sont des tampons trois-états, ce qui permet de déconnecter les portions inutilisées de la ligne de bit. Ce faisant, la fenêtre d'instruction est découpée en segments, qui peuvent être désactivés si besoin. Si il y a peu d'instructions chargées dans la fenêtre d'instruction, les portions inutilisées sont désactivées. La technique marche d'autan mieux si les instructions sont compactées au début de la fenêtre d'instruction.
[[File:Wire partitionning.png|centre|vignette|upright=2|Wire partitionning]]
Tout le problème est de savoir quelles portions désactiver. La technique est assez simple si la fenêtre d'instruction est compactée, à savoir si toutes les instructions sont régulièrement regroupées au début de la mémoire CAM. Mais même avec cette optimisation, la décision de désactiver une portion de la fenêtre d'instruction n'est pas à prendre à la légère. Il ne faut pas désactiver une portion contenant des instructions pouvant être émise sous peu. La décision dépend de paramètres variés : proportion d'entrée vides, quantité d'entrées récemment activées/désactivées récemment, présences d'entrées avec un ''match'' d'opérandes récent, etc.
Il est important de préciser que les techniques en question fonctionnent aussi sur les autres types de fenêtre d'instruction, qui ne sont pas basées sur des mémoires associatives. Nous allons voir celles-ci dans la suite du chapitre, mais gardez à l'esprit que ces optimisations, à savoir désactiver les entrées vides/prêts et partitionner la fenêtre d'instruction, sont des optimisations générales qui marchent avec presque tout.
===L'ajout d'une FIFO pour les instructions déjà prêtes à l'émission===
D'autres techniques permettent de réduire aussi bien le cout en circuits que la consommation d'énergie de la fenêtre d'instruction. L'idée est d'utiliser une mémoire associative quand elle est réellement nécessaire, mais d'utiliser une fenêtre d'instruction "normale" dès que possible.
La méthode la plus simple tient compte du fait que certaines instructions ont déjà toutes leurs opérandes de disponibles à l'émission, mais doivent quand même être mises en attente, parce que l'ALU adéquate est occupée, parce que des dépendances WAW/WAR sont encore là, ou pour tout autre raison. Dans ce cas, elles sont mises en attente non pas dans la fenêtre d'instruction, mais dans une mémoire FIFO toute simple. L'idée est qu'au lieu d'avoir une énorme mémoire associative pour la fenêtre d'instruction, on déplace quelques entrées dans une petite FIFO annexe. Le résultat est un gain en terme d'énergie et de circuits, au prix d'une perte de performance mineure. La perte de performance en question se manifeste si aucune instruction n'a ses opérandes de prêtes à l'émission : le programme n'utilise pas la FIFO et voit alors une fenêtre d'instruction plus petite.
Une amélioration de cette technique tient compte du fait que certaines instructions sont dans un cas intermédiaire. Elles ont déjà une opérande de prête lors de l'émission, mais pas l'autre. Le nombre d'opérandes varie suivant l’instruction : certaines n'ont besoin que d'un seul opérande. De plus, il arrive qu'une opération tout juste décodée ait déjà un ou plusieurs de ses opérandes de prêts. Cette constatation permet d'éviter d'utiliser des comparateurs pour les opérandes déjà prêts. Par exemple, on peut parfaitement utiliser trois fenêtres d'instruction :
* une pour les instructions dont tous les opérandes sont prêts, mais qui sont quand même mises en attente, sans comparateur ;
* une pour les instructions dont un seul opérande manque, qui n'utilise qu'un comparateur par entrée ;
* une pour les instructions dont deux opérandes manquent à l'appel, qui utilise deux comparateurs par entrée.
C'est beaucoup plus économique que d'utiliser une seule grosse fenêtre d'instruction qui contiendrait autant d'entrées que les trois précédentes réunies, avec deux comparateurs par entrée. Certains sont même allés plus loin, et ont proposé de supprimer la fenêtre d'instruction avec deux opérandes par entrée. Les instructions dont deux opérandes sont inconnus au décodage sont stockées dans la fenêtre d'instruction pour instructions avec un comparateur par entrée. Un circuit de prédiction se charge alors de prédire l'opérande manquant, cette prédiction étant vérifiée plus loin dans le pipeline. Il serait cependant étonnant qu'une telle proposition ait donné lieu à la moindre implémentation réelle.
===Les optimisations de la consommation d'énergie avancées===
D'autres chercheurs ont conservé une fenêtre d'instruction unique, avec autant de comparateurs par entrée qu'il y a d'opérandes possibles par instruction. Simplement, le processus de détection des opérandes prêts est légèrement ralenti : on ne vérifie qu'un opérande par cycle. Pour vérifier les deux opérandes d'une entrée, on doit attendre deux cycles.
Enfin, certains chercheurs ont proposé des fenêtres d'instruction segmentées. Les instructions circulent à chaque cycle d'un segment vers le suivant : en conséquence, certains segments conservent les instructions les plus anciennes, un autre les instructions les plus jeunes, etc. Seul le denier segment, celui qui contient les instructions les plus vielles, peut émettre une instruction : la détection des opérandes se fait seulement dans le dernier segment, qui contient les instructions les plus anciennes. On économise ainsi beaucoup de comparateurs.
Certains chercheurs ont tenté de pipeliner l'étape de sélection des opérandes, ainsi que l'étage d'arbitrage. Mais en faisant cela, il faut plusieurs cycles pour détecter qu'une instruction a ses opérandes prêts, ce qui pose problème face à de nombreuses dépendances RAW. Pour éviter de trop perdre en performances, certains chercheurs ont décidé d'utiliser des techniques pour prédire quelles seront les instructions dont les opérandes seront bientôt prêts. Si détecter qu'une instruction est prête prend n cycles, le processeur devra tenter de prédire la future disponibilité des opérandes n cycles en avance pour obtenir des performances optimales.
===Remplacer la mémoire associative par une RAM===
Une autre optimisation possible est de remplacer la fenêtre d'instruction par une mémoire RAM, dont chaque mot mémoire correspondrait à une entrée. Une telle optimisation permet en théorie de se passer des comparateurs associés à chaque entrée, mais au prix de l'ajout de circuits annexes potentiellement couteux.
La première de ces techniques, la '''recherche directe par étiquette''' (direct tag search) fut créée par Weiss et son collègue Smith. Ils cherchaient à améliorer l'algorithme de Tomasulo, un algorithme qu'on expliquera dans quelques chapitres. Rappelons que chaque instruction produit un résultat, qui devra être rapatrié dans une ou plusieurs entrées de la fenêtre d'instruction. L'optimisation de la recherche directe par étiquette fonctionne dans le cas où le résultat de l'instruction n'est utilisé que par une seule entrée, et pas plusieurs.
Le principe est simple : chaque champ « opérande » d'une entrée est adressable via le nom de registre de cette opérande. Pour faire le lien entre entrée et nom de registre, la recherche directe par étiquette ajoute une table de correspondances matérielle, une mémoire RAM qui mémorise les adresses des champs « opérande » des entrées. Les adresses envoyées dans cette mémoire sont les noms de registres des résultats. Lors de l'émission, la table de correspondances est mise à jour. Les numéros des champs « opérande » réservés lors de l'émission sont mémorisés dans les mots mémoire qui correspondent aux deux registres sources. Toutefois, une petite vérification est faite lors de l'émission : si il y a déjà une correspondance dans la table pour un registre source, alors une autre instruction compte lire ce registre. Pour éviter d'écraser les données de l'instruction précédente dans la table de correspondances, l'émission de l'instruction est bloquée.
[[File:Recherche directe par étiquette.png|centre|vignette|upright=2|Recherche directe par étiquette.]]
==Les fenêtres d’instruction avec préplanification==
Avec la '''préplanification''' (''prescheduling''), la fenêtre d’instruction est composée de mémoires FIFO dans lesquelles les instructions sont triées dans l'ordre d'émission. Il n'y a pas de logique d’émission proprement dite, celle-ci se bornant à vérifier si l'instruction située au début de la FIFO peut s’exécuter. Par contre, le préplanificateur détecte les dépendances et en déduit où insérer les instructions dans le tampon d'émission, à l'endroit le plus adéquat.
[[File:Préplanification.jpg|centre|vignette|upright=2|Préplanification.]]
===L'usage de FIFO multiples===
La technique de préplanification la plus simple utilise plusieurs FIFO. Quand une instruction a une dépendance avec une autre instruction, les deux sont placées dans la même FIFO. Pour cela, le préplanificateur détecte les chaines d'instructions dépendantes, et place les instructions dans la FIFO adéquate.
A chaque cycle, l'unité d'émission lit une micro-opération dans chaque FIFO et vérifie si elle peut l'émettre. Si la micro-opération n'a pas ses opérandes disponibles, ou qu'une dépendance structurelle survient, alors la FIFO est bloquée. La micro-opération attend que la dépendance soit résolue, bloquant toutes les instructions précédentes. Mais les autres FIFO ne sont pas bloquées, laissant de la marge de manœuvre.
[[File:Préplanification par FIFO multiples.png|centre|vignette|upright=1.5|Préplanification par FIFO multiples.]]
===La technique du tampon trié===
La technique du '''tampon trié''' trie les instructions selon le temps d'attente avant leur exécution. Les instructions qui sont censées s’exécuter bientôt sont insérées au tout début de la FIFO, tandis que les instructions qui s’exécuteront dans un long moment sont insérées vers la fin. Là encore, l'unité d'émission lit une micro-opération dans la FIFO, et détermine si elle peut être émise. Si une dépendance survient, l'émission est retardée, et la FIFO est bloquée.
[[File:Préplanification par tampon trié.png|centre|vignette|upright=1.5|Préplanification par tampon trié.]]
La technique a cependant quelques problèmes assez importants. Le premier problème est que la FIFO est bloquée lorsqu'une micro-opération ne peut être émise. Le second problème est que le temps avant exécution n'est pas toujours connu, notamment pour les instructions d'accès mémoire. Et cela se répercute sur les instructions dépendantes de celles-ci. Pour résoudre ce genre de problèmes, l'usage d'un pipeline à ''replay'' est possible, et est même l'une des meilleures solution possible, malgré ses défauts.
L'idée est que les lecture sont pré-planifiées en supposant qu'elles font un succès de cache L1. Si la prédiction est fausse, la lecture est ré-exécutée plusieurs cycles plus tard, en supposant qu'elle fait un succès dans le cache L2, et ainsi de suite. Elle est alors ré-insérée dans la FIFO, elle est pré-planifiée une seconde fois. Il en est de même si une micro-opération ne peut pas être émise : il suffit de la réinsérer dans la FIFO au bon endroit, en attendant que ses dépendances soient résolus. Ainsi, l'instruction fautive ne bloque pas la fenêtre d’instruction, et retente sa chance autant de fois qu'il le faut.
[[File:Préplanification par fenêtre d’instruction scorebarodée.png|centre|vignette|upright=1.5|Préplanification par fenêtre d’instruction scorebarodée.]]
Une autre solution utilise deux fenêtres d’instruction : une FIFO gérée par la préplanification, et une vraie fenêtre d'instruction pour les instructions dont le temps d'attente ne peut pas être déterminé. Il est possible de mettre cette dernière avant la préplanification, afin de gérer les temps d'attente des instructions d'accès mémoire. Quand le temps d'attente d'une lecture devient connu, ses instructions dépendantes sont gérées avec préplanification.
[[File:Préplanification avec fenêtre d’instruction.png|centre|vignette|upright=2.5|Préplanification avec fenêtre d’instruction.]]
===Les fenêtres d'instruction avec allocation temporelle statique===
L''''allocation temporelle statique''' correspond à une technique utilisée sur les processeurs Cuzco de l'entreprise Condor. La technique en question a été présenté en 2025, mais des idées similaires ont été présentées dans la littérature académique. Elle ressemble beaucoup aux techniques de pré-planification, avec cependant quelques différences.
L'idée est simple : le processeur dispose de plusieurs fenêtres d'instruction, qui mettent en attente les micro-opérations. Lors de l'étape de renommage de registres, le processeur détermine dans combien de cycles d'horloge la micro-opération sera prête pour exécution. La micro-opération est alors mise en attente durant ce nombre de cycles, dans les fenêtres d'instruction, puis est émise une fois ce nombre de cycles écoulés. Ce faisant, les fenêtres d'instruction n'ont pas besoin de détecter la disponibilité des opérandes à chaque cycle, pour chaque micro-opération en attente. Le ''timing'' de l'émission des micro-opérations est décidé à l'avance.
L'implémentation exacte n'est pas connue, mais plusieurs solutions sont imaginables. Par exemple, la fenêtre d'instruction peut simplement être composée de plusieurs mémoires de petite taille, chacune contenant les micro-opérations destinées à être exécutées dans N cycles, N étant différent pour chaque mémoire. Une autre solution, plus réaliste, utilise une fenêtre d'instruction dans laquelle la micro-opération est couplée à des champs pour les opérandes, et un champ compteur. Le champ compteur est initialisé avec le nombre de cycles à attendre, et est décrémenté à chaque cycle d'horloge. Quand il atteint zéro, la micro-opération est émise.
Vous remarquerez que la méthode ne marche que si toutes les micro-opérations prennent un nombre fixe de cycles d'horloge. Et autant c'est le cas pour les micro-opérations arithmétiques ou logiques, autant les accès mémoire ne rentrent pas dans ce cadre. En théorie, on ne sait pas combien de temps prendra un accès mémoire. Et cette incertitude se répercute sur les instructions dépendantes d'un accès mémoire.
Pour éviter cela, le processeur réutilise un pipeline à ''replay'' tel que vu dans le chapitre précédent. L'unité de renommage de registre suppose que tout accès mémoire fait un succès de cache L1, et décide des ''timings'' d'émission sous cette hypothèse. Si une lecture subit un défaut de cache, elle est ré-exécutée, de même que toutes les instructions dépendantes. Pour cela, le registre de destination de la lecture est marqué comme invalide, grâce à un bit spécifique attaché au registre. Toute instruction qui lit une opérande invalide est elle aussi ré-executée de zéro.
Pour simplifier, l'unité d'émission ressemble à une unité d'émission dans l'ordre, avec un registre de disponibilité, qu'on aurait amélioré pour aller au-delà de la simple détection des dépendances d'instruction. Pour déterminer les ''timings'' d'émission, à savoir quand émettre une micro-opération, le processeur utilise une unité d'émission à deux étages. Le premier étage est appelé le ''register scoreboard'', le second étage est appelé la ''Time Ressource Matrix''. Les deux étages communiquent entre eux, comme on va le voir, et leurs noms donnent une idée de ce qu'ils font. Le premier étage gère les dépendances de registres, le second gère les dépendances structurelles.
Le premier étage est un registre de disponibilité amélioré, qui gère les dépendances de registre. Cet étage sait quand tel registre sera écrit, et donc que l'opérande écrite dedans sera disponible à partir de tel cycle d'horloge. Il le sait car l'étage suivant l'aura prévenu, mais laissons cela de côté pour le moment. Quand une micro-opération rentre dans cet étage, il vérifie les dépendances avec les registres et les micro-opérations antérieures. Vu qu'il sait quand les registres seront disponibles, il peut déterminer quand l'instruction pourra s'exécuter. Par exemple, si une micro-opération lit les deux registres R7 et R15, qui sont disponibles respectivement dans 5 et 7 cycles, le premier étage sait que la micro-opération devra attendre 7 cycles.
Mais tout cela ne suffit pas à déterminer quand émettre une micro-opération. En effet, il fait aussi gérer les dépendances structurelles, ce qui est le boulot du second étage. Le second étage utilise pour cela une ''Time Ressource Matrix'', qui mémorise l'occupation de diverses "ressources", pour les 256 cycles d'horloge à venir. Les ressources en question sont les ports de lecture/écriture du banc de registre, les ALUs utilisées, si la micro-opération utilise l'unité mémoire, etc. Le second étage prend en entrée une micro-opération, détermine quelles "ressources" elle utilise, puis regarde leur disponibilité. Elle détecte alors les dépendances structurelles et détermine alors quand peut s'exécuter la micro-opération. Elle peut retarder l'émission de quelques cycles, si une dépendance structurelle est détectée.
Une fois sortie du second étage, on sait quand la micro-opération va être émise, à quel cycle précisément. Et vu que la durée de l'instruction est fixe, on sait quand cette micro-opération va se terminer, quand son résultat sera disponible. Cela permet de mettre à jour la ''Time Ressource Matrix'', pour préciser que le port d'écriture sera occupé à tel moment. De plus, cette information est transmise au premier étage, qui sait alors quand est enregistré le résultat dans le registre de destination. C'est comme cela que le premier étage sait quand un registre est disponible : le second étage le prévient, quand il émet une instruction !
La ''Time Ressource Matrix''mémorise l'occupation des "ressources" pour les 256 cycles d'horloge à venir. Cependant, elle ne vérifie pas les dépendances pour les 256 cycles suivants. Quand elle reçoit une micro-opération, elle teste les dépendances structurelles pour seulement les 8 prochains cycles maximum. Si la micro-opération ne peut pas être émise pendant ces 8 cycles, le pipeline est bloqué, un ''pipeline stall'' est émis. Pour être plus précis, vu que le processeur peut décoder 8 micro-opérations en même temps, cela permet de ne consulter que 64 cycles d'horloge sur les 256.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les premiers processeurs Intel
| prevText=Les premiers processeurs Intel
| next=Le renommage de registres
| nextText=Le renommage de registres
}}
</noinclude>
lh48wejf85rj6hx4vw6mi220qd8vdx4
773349
773348
2026-09-27T20:31:27Z
Mewtow
31375
/* Un cas extrême : les fenêtres d'instruction totalement décentralisées */
773349
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons parlé des techniques d'émission dans l'ordre. L'idée est d’exécuter une nouvelle instruction à chaque cycle, dans des unités de calcul séparées. Les instructions sont émises consécutivement et l'unité d'émission bloque l'émission en cas de dépendance, mais les instructions s’exécutent en parallèle dans des unités de calcul séparées. De plus, elles peuvent se finir dans le désordre, en absence de dépendances, si les exceptions sont imprécises. Le point important est qu'on doit exécuter des instructions indépendantes dans des unités de calcul séparées.
Les techniques d'exécution dans le désordre que nous allons voir dans ce chapitre sont dites à '''émission dans le désordre'''. Elles se distinguent des précédentes sur un point précis : l'unité d'émission bloque l'émission d'une instruction, elle seule est bloquée, les instructions suivantes peuvent poursuivre. L'émission des instructions peut donc se faire dans le désordre, dans le sens où une instruction peut être émise alors qu'une instruction précédente ne l'est pas encore.
L'avantage de l'émission dans le désordre est qu'une instruction s'exécute dès que ses opérandes sont disponibles. Le processeur incorpore de quoi déterminer quand un résultat est disponible, de quoi savoir quand un opérande est prête. Par prête, on veut dire présente soit dans le réseau de contournement, soit enregistrée dans les registres. Dès qu'une instruction a ses opérandes disponibles, elle est émise et exécutée. C'est là la seule contrainte : les dépendances WAR, WAW et autres sont gérées par un ROB ou toute autre technique d'exception précise, seules les dépendances RAW limitent l'exécution.
: Dans la suite, nous utiliserons parfois l'abréviation OOO pour parler d'exécution dans le désordre. L'abréviation est celle de ''Out Of Order Excution''.
Mais avant de poursuivre, précisons une chose importante : l'exécution dans le désordre traite les accès mémoire à part. La majorité des processeurs OOO assez anciens exécutent les accès mémoire dans l'ordre, seules les instructions entières/flottantes/branchements sont exécutées dans le désordre. L'exécution dans le désordre des accès mémoire est courante sur les processeurs récents, mais c'est l'unité d'accès mémoire qui se charge de changer l'ordre des accès mémoire toute seule dans son coin. Nous allons volontairement mettre de côté la gestion des accès mémoire dans ce chapitre.
==Les fenêtres d’instruction : généralités==
Pour implémenter l'exécution dans le désordre, il faut ajouter une sorte de mémoire qui met en attente les instructions bloquées, à émettre. Les instructions attendent dans cette mémoire le temps que leurs opérandes soient disponibles. De plus, il faut un circuit capable de gérer les dépendances RAW. L'unité d'émission qui bloquait les instructions, ne bloque plus rien, car elle faisait office de dépendance structurelle qui est éliminée.
[[File:File de micro-opération.png|vignette|upright=1|File de micro-opérations]]
Les processeurs sans exécution dans le désordre ont une unité d'émission qui bloque tout le pipeline dès qu'une instruction ne peut pas être émise. Pour limiter l'impact de ce blocage, quelques rares processeurs incorporent une mémoires FIFO appelée la '''file d'instruction''', aussi appelée '''file de micro-opération'''. Elles permettent d'émettre des instructions dans l'ordre, à savoir qu'elles quittent la file d'instruction dans l'ordre d'ajout, dans l'ordre de décodage. Le schéma ci-dessous illustre une telle file d'attente couplée à un ''scoreboard''.
Les techniques modernes d'OOO utilisent aussi des files de micro-opération améliorées, comme on le verra plus bas. De telles files de micro-opération permettent d'émettre des instructions dans le désordre, ce qui corrige le problème mentionné plus haut avec le ''scoreboard''. Si une instruction bloque le pipeline, les instructions suivantes peuvent être émises.
Il existe dans les grandes lignes 4 grandes techniques d'exécution dans le désordre. Elles peuvent se classer sur deux critères : est-ce que les instructions sont émises dans l'ordre, et combien il y a de files de micro-opération.
{|class="wikitable"
|-
!
! Emission dans l'ordre
! Emission dans le désordre
|-
! Une file de micro-opération
| ''Scoreboard''
| Fenêtre d'instruction centralisée
|-
! Plusieurs files de micro-opération
| Plusieurs FIFOs, plusieurs ''Scoreboard'' indépendants
| Fenêtre d'instruction décentralisée
|}
===L'usage de plusieurs files d'instruction===
La première méthode évoluée d'OOO utilise plusieurs files de micro-opération. L'idée est qu'une fois décodée, les instructions sont accumulées dans des files de micro-opération différentes. Il y a typiquement une file de micro-opération pour les opérations entières, une autre pour les opérations flottantes, une autre pour les accès mémoire. D'autres processeurs utilisent une file pour les accès mémoire et une autre pour les autres instructions, comme le faisait le Pentium 4 d'Intel.
Les files de micro-opération sont des mémoires FIFO, ce qui fait qu'elles conservent l'ordre des instructions. Et chaque FIFO est couplée à un ''scoreboard'' qui vérifie les dépendances à chaque cycle. La présence de ''scoreboard'' nous dit que l'intérêt n'est pas un mécanisme d'émission plus compliqué, mais que la technique se repose sur le fait d'avoir plusieurs files de micro-opération. L'intérêt d'avoir plusieurs FIFOs est que si une dépendance bloque une FIFO, les autres peuvent continuer d'émettre des instructions.
Les files de micro-opération émettent leurs instructions de manière indépendante, ou presque. Elles ne savent pas si telle instruction dans une autre file doit passer avant ou après l'instruction qu'elles émettent. Par contre, les unités d'émission communiquent entre eux pour gérer la disponibilité des registres. Quand un registre est lu ou écrit par une instruction, les autres files d'instruction doivent être prévenues et mettre à jour leurs unités d'émission. La logique d'émission est donc plus complexe que prévu. Le processeur Pentium 4 avait deux FIFOs séparées : une pour les instructions d'accès mémoire et une autre pour les autres instructions. Si jamais une instruction mémoire bloquait le pipeline, l'autre FIFO pouvait continuer à exécuter des instructions entières indépendante de la lecture bloquée.
Les FIFOs sont simples à implémenter et ont un cout en circuit modéré. Par contre, le gain en performances est assez limité avec cette méthode, surtout comparé aux méthodes qui vont suivre. Le problème est qu'il est facile de se retrouver avec toutes les files de micro-opération bloquées. Quand l'une est bloquée, les autres tendent à se vider rapidement, particulièrement quand c'est la file pour les accès mémoire qui se bloque. Les techniques qui vont suivre font totalement disparaitre ce genre de blocage.
===La fenêtre d'instruction centralisée===
Les processeurs OOO modernes utilisent une file d'attente modifiée, où les instructions sont insérées dans l'ordre, mais peuvent sortir dans le désordre, être émises dans le désordre. La file d'attente en question est appelée la '''fenêtre d'instruction'''. Les instructions décodées sont accumulées dans la fenêtre d'instruction, où elles attendent que leurs opérandes soient disponibles. La fenêtre d'instruction sert donc de file d'attente. A chaque cycle, l'unité d'émission consulte la fenêtre d'instruction pour voir quelles instructions peuvent s'exécuter, quelles instructions ont leur opérandes de disponibles. Elle choisit une instruction exécutable et l'envoie aux ALU. La fenêtre d'instruction doit spécialement être conçue pour, c'est une sorte de mémoire associative très complexe.
[[File:OOO Issue.png|centre|vignette|upright=2.5|Exécution dans le désordre avec une fenêtre d'instruction]]
Le cas le plus simple n'utilise qu'une seule fenêtre d'instruction. Avec elle, l'unité d'émission, détecte les dépendances et répartit les instructions sur les unités de calcul. Les instructions décodées sont ajoutées en parallèle dans la fenêtre d'instruction, et le ROB. La raison est que les instructions quittent la fenêtre d'instruction dans le désordre, l'ordre des instructions est perdu après l'unité de décodage/renommage de registre. Aussi, pour remplir le ROB, la seule opportunité est en sortie de l'unité de décodage. Dans le cas où une instruction est décodée en plusieurs micro-opérations, elles sont toutes insérées en même temps dans la fenêtre d'instruction et le ROB.
[[File:Fenêtre d'instruction.png|centre|vignette|upright=2|Fenêtre d'instruction.]]
Les micro-opérations mémoire sont à part des autres, pour diverses raisons. Une de ces raisons est que les dépendances de données sont classées en deux types : les dépendances de registre et les dépendances d'adresse. Et les deux sont fondamentalement différentes. Autant on peut détecter les dépendances de registre lors de l'émission, autant c'est plus compliqué avec les dépendances d'adresse. De plus, rappelons que si les lectures peuvent s'exécuter dans le désordre, les écritures doivent s'exécuter dans l'ordre, ou du moins en donner l'illusion. Pour cela, le processeur remet les écritures dans l'ordre avec une sorte de mini-ROB intégré à l'unité mémoire, appelée la file d'écriture. Elle complémente le ROB, qui lui ne gére que les écritures dans les registres, mais les deux collaborent.
Les processeurs grand public anciens n'autorisaient pas d'exécution dans le désordre des accès mémoire. Pour cela, l'unité mémoire est précédée par une file de micro-opérations, pour forcer l'exécution dans l'ordre des accès mémoire. Les processeurs modernes ajoutent des techniques d'exécution dans le désordre des accès mémoire, mais nous les verrons dans un chapitre dédié. Dans les deux cas, les micro-opérations mémoire doivent être envoyées à l'unité mémoire dans l'ordre du programme, dans l'ordre de décodage. Dans ce chapitre, on suppose que l'unité mémoire est précédée d'une file de micro-opération mémoire dédiée, rien que pour elle. Elle est à part du reste.
[[File:Processeur avec émission dans l'ordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans l'ordre des accès mémoire]]
===Les fenêtres d'instruction décentralisées===
Une autre solution utilise plusieurs fenêtres d'instruction. Pour faire la différence avec l'usage d'une fenêtre d'instruction unique, nous allons parler de fenêtre d'instruction centralisée s'il n'y en a qu'une dans le processeur, de '''fenêtre d'instruction décentralisée''' s'il y en a plusieurs. Dans le cas général, chaque fenêtre d'instruction est associée à un type précis d'opération/instruction. La tendance moderne est d'utiliser une fenêtre d'instruction pour les instructions de calcul entières, une autre pour les flottantes. Il s'agit d'un choix simple et efficace, mais quelques processeurs font autrement, pour répondre à des contraintes très diverses.
L'usage de fenêtres décentralisées simplifie la répartition des instructions sur les différentes unités de calcul. Par exemple, utiliser des fenêtres séparées pour les ALU et les FPU facilite la répartition des instructions de calcul sur les ALU. Une fenêtre centralisée demande de faire la différence entre instruction entière/flottante, puis de choisir sur quelle unité de calcul entière/flottante utiliser. Une fenêtre décentralisée fait les deux choix séparément : d'abord on envoie l'instruction dans la bonne fenêtre suivant si c'est une instruction flottante ou entière, et ensuite on gère la disponibilité des ALUs/opérandes. Et on peut adapter la même idée en séparant les unités d'accès mémoire des ALU entières, etc.
Par contre, un défaut est que certaines fenêtres d'instruction peuvent être sous-utilisées. Par exemple, la station de réservation flottante est inutilisée si le programme n'exécute pas d'opérations flottantes. Ou encore, les fenêtres pour les accès mémoire peuvent être sous-utilisées, si le programme effectue peu d'accès mémoire relativement aux autres instructions. En fait, tout dépend de comment sont calibrées les fenêtres d'instruction et de la répartition des instructions dans le programme entre opérations entières, flottantes, mémoire. Par exemple, si un programma de deux fenêtres d'instructions identiques, une pour les calculs entiers et une autre pour les calculs flottants, elles ne seront utilisées à la perfection que si la moitié des instructions du programme fait des calculs flottants et l'autre des calculs entiers. Toute déviation par rapport à cette répartition idéale risque de laisser des entrées vides dans une fenêtre d’instruction.
Un point important est que l'émission est découpée en deux étages avec des fenêtres d'instruction décentralisées. La première étape envoie l'instruction décodée vers la fenêtre d'instruction adéquate, la seconde gère l'émission proprement dit. La première est l'étage de ''dispatch'' qui envoie l'instruction dans la fenêtre d’instruction adéquate, selon que c'est une instruction flottante, entière, autre. La seconde est l'étage de ''scheduling'', qui gère la disponibilité des opérandes et des unités de calcul. En clair, on trie les instructions suivant leur type, suivant le type d'unité de calcul adéquate, puis on l'émet proprement dit. Faire ainsi a plusieurs avantages, avec cependant peu d'inconvénients.
Lors de l'étape de ''dispatch'', l'instruction émise est ajoutée au tampon de réordonnancement, s'il existe. Sans cela, impossible de conserver l'ordre des instructions. Les instructions sont émises dans l'ordre au niveau de l'unité de ''dispatch'', pas au niveau de l'étage de ''scheduling''. Aussi, pour remplir le ROB, cela doit se faire au dernier moment où les instructions sont émises dans l'ordre, soit en sortie de l'unité de ''dispatch''. De plus, les micro-opérations mémoire sont traitées à part des autres, elles sont envoyées directement à l'unité mémoire. La gestion des calculs d'adresse est quelque peu complexe, mais ils peuvent être fait soit dans l'unité mémoire, soit dans les ALU entières, peu importe. Il y a aussi un système de contournement complexe entre ALU/FPU et unité mémoire, qui n'est pas représenté dans le schéma ci-dessous.
[[File:Processeur avec plusieurs fenêtres d'instruction.png|centre|vignette|upright=2.5|Processeur avec plusieurs fenêtres d'instruction.]]
===Un cas extrême : les fenêtres d'instruction totalement décentralisées===
Prenons un cas extrême de fenêtre d'instruction décentralisée : celui où chaque ALU entière a sa propre station de réservation. En clair : une fenêtre d'instruction par ALU/FPU ! On parle de '''fenêtres d'instruction totalement décentralisées'''. Vous vous dites que ce cas est un peu trop extrême pour être réaliste, mais il n'en est rien. C'est même très courant sur les architectures basse consommation, et sur pas mal de CPU x86 modernes.
Un cas aussi extrême entraine une légère perte de performance, comparé à une fenêtre d'instruction centralisée ou partiellement décentralisée. Mais elle entraine aussi un gain considérable en termes de consommation énergétique. Et c'est la raison pour laquelle les fenêtres d'instructions totalement décentralisées sont utilisées sur les architectures basse consommation. La perte de performance ne peut pas s'expliquer clairement à ce stade du cours, car il nous faudrait parler d'émission superscalaire. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
Il faut noter que quelques processeurs utilisent des méthodes intermédiaires entre les trois solutions précédentes. Par exemple, les processeurs AMD Zen utilisent une fenêtre d'instruction décentralisée un peu particulière. Les unités de calcul entières disposent d'une fenêtre d'instruction rien que pour elles, sur ce processeur. Les instructions flottantes sont quant à elles alimentées par une file de micro-opération, pas une fenêtre d'instruction ! La raison à ce choix est un compromis entre performance et cout en circuit. L'exécution dans le désordre est peu efficace sur les instructions flottantes, car les CPU n'ont que peu d'ALU flottantes séparées. Vu le cout en circuits d'une fenêtre d'instruction, les concepteurs du processeur ont préféré utiliser une file de micro-opération pour les opérations flottantes.
===Avantages et inconvénients de chaque implémentation===
Un processeur contient soit une fenêtre d'instruction unique, soit plusieurs fenêtres d'instruction séparées. Typiquement, les fenêtres d’instruction séparées sont spécialisées, dans le sens où on a une fenêtre pour les instructions flottantes, une autre pour les instructions entières, une autre pour les accès mémoire, etc. Pour résumer, on a le choix entre une grosse fenêtre généraliste et plusieurs fenêtres spécialisées, sauf que la fenêtre centralisée est lente et complexe, contrairement aux fenêtres spécialisées. Les deux méthodes ont des inconvénients et des avantages différents.
Dans les grandes lignes, l'avantage des fenêtres décentralisées est qu'elles sont plus petites qu'une grosse fenêtre d'instruction. Cela simplifie la gestion du contournement et de la répartition des instructions sur les unités de calcul, comme on le verra dans quelques paragraphes. Elles sont donc moins complexes et plus rapides. L'autre avantage, c'est qu'il est possible de démarrer l'exécution de plusieurs instructions simultanément : une instruction par fenêtre décentralisée, contre une par fenêtre d'instruction.
Mais les fenêtres décentralisées sont souvent sous-utilisées, partiellement remplies, contrairement aux fenêtres d'instruction. Il arrive qu'une fenêtre d'instruction soit remplie, alors que les autres sont vides. Par exemple, prenons un processeur avec une fenêtre d'instruction reliée à la FPU, et une autre reliée aux autres ALUs. Si le processeur n'exécute que des opérations flottantes, la fenêtre reliée à la FPU sera pleine, alors que l'autre sera vide. L'exécution des instructions dans le désordre est alors limitée par la petite taille de la fenêtre, qui ne peut plus accepter de nouvelle instruction flottante. Avec une fenêtre unique, on n'aurait pas eu ce problème : on aurait eu une énorme fenêtre d'instruction remplie d'instruction flottante, au lieu d'une petite fenêtre spécialisée.
Il est maintenant temps de voir en quoi sont faites les fenêtres d’instruction, qu'elles soient centralisées ou décentralisées. Nous allons aussi voir quelle est la logique d'émission associée.
==L'implémentation d'une fenêtre d'instruction : généralités==
Une fenêtre d'instruction est une mémoire qui mémorise des micro-opérations. Elle est cependant plus complexe qu'une mémoire RAM ou qu'une FIFO et ressemble un petit peu à une mémoire cache. Elle dispose d'un port d'écriture et d'un port de lecture, au minimum.
Le port d'écriture permet à l'unité de décodage d'insérer une micro-opération dedans, d'ajouter la micro-opération qui vient d'être décodée. Si une instruction machine est décodée en plusieurs micro-opération, deux possibilités. La première est de les insérer un par un, ce qui fait qu'on peut se contenter d'un seul port d'écriture. L'autre possibilité est de les ajouter en même temps, ce qui demande plusieurs ports d'écriture.
Le port de lecture sert à émettre une instruction. On part du principe que la fenêtre d'instruction ne peut émettre qu'une seule instruction à la fois, car les processeurs que nous avons vu précédemment sont de ce type. Mais nous verrons dans quelques chapitre qu'il existe des processeurs dits superscalaires, qui sont capables d'émettre plusieurs instructions par cycle. Dans ce cas, la fenêtre d'instruction a un seul port de lecture. Un processeur superscalaire, quant à lui, doit avoir un port de lecture par unité de calcul, ce qui fait beaucoup plus.
===Les entrées d'une fenêtre d’instruction===
Les fenêtres d'instruction sont composées d''''entrées''', des mots mémoire qui stockent une instruction. Une instruction réserve une entrée après son décodage et la libère dès qu'elle est envoyée aux unités de calcul. Une entrée contient l'opcode (les signaux de commande à envoyer à l'ALU), le registre de destination du résultat, les registres de chaque opérande, et éventuellement des signaux de commande en plus. De plus, chaque opérande est couplée à un bit de disponibilité, qui indique si elle est disponible. Quand un opérande est écrit dans les registres, le bit de présence correspondant est mis à jour. Enfin, chaque entrée possède un bit ''empty'' qui indique si elle est vide, cette information étant utile pour réserver des entrées.
[[File:Entrée d'une station de réservation - sans les tags.png|centre|vignette|upright=3|Entrée d'une fenêtre d'instruction]]
Nous verrons dans quelques chapitres que certaines fenêtres d'instruction sont capables de mémoriser les opérandes des instructions. De telles fenêtres d'instruction seront appelées, dans ce cours, des '''stations de réservation'''. Les entrées des stations de réservation sont assez semblables à celle des fenêtres d'instruction, à un détail près : les opérandes de l'instruction sont enregistrées directement dans des champs séparés. Il y a deux champs, un par opérande, pour stocker l'opérande elle-même, lue depuis les registres ou obtenue via le réseau de contournement. Notons que les champs pour les opérandes viennent en plus des champs pour les noms de registres, encore que les deux peuvent être fusionnés si on veut optimiser le tout.
[[File:Entrée d'une station de réservation.png|centre|vignette|upright=3|Entrée d'une station de réservation.]]
Précisons que la terminologie n'est pas très fiable. Les termes "fenêtre d'instruction" et "station de réservation" ne regroupent pas du tout la même chose d'un papier de recherche à l'autre, d'un bouquin à l'autre, d'un chercheur à l'autre, d'un ingénieur à l'autre, d'un professeur à l'autre. Le terme initial vient pourtant de l'article original sur l'algorithme de Tomasulo, où il désignait une fenêtre d'instruction associée à une unité de calcul précise. Mais le terme a ensuite été réutilisé, ce qui fait que certains processeurs avec plusieurs unités de calcul sont décrit comme ayant une unique station de réservation. En bref : c'est le bazar ! Mais nous en reparlerons dans le chapitre sur le renommage de registres, car c'est la place idéale pour les aborder.
Toujours est-il que j'ai fait le choix de confondre les concepts de fenêtre d'instruction et de station de réservation avec les concepts de '''lecture avant émission''' et de '''lecture après émission'''. La différence entre les deux est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation. Dans le cas "avant émission", les opérandes sont mémorisées dans la station de réservation. Et cette différence a une influence sur le pipeline du processeur, le banc de registres et tout ce qui s'en suit. Un désavantage de la lecture avant émission est qu'il faut stocker les opérandes après lecture, ici dans les stations de réservation. Mais un avantage est que le banc de registre a besoin de moins de ports de lecture, car une bonne partie des opérandes sont fournies par le système de contournement. Nous verrons cela dans le détail dans le chapitre sur les processeurs superscalaires.
Il a été proposé de fusionner le tampon de ré-ordonnancement avec la fenêtre d'instruction dans une structure unique. La méthode a notamment été utilisée sur les processeurs d'architecture K6 d'AMD. Le tout donnait une structure appelée le '''DRIS''' (''deferred scheduling register renaming instruction shelf''). La différence avec une fenêtre d’instruction normale est que les entrées sont libérée non pas quand l'instruction est exécutée/émise, mais quand elle termine son exécution et quitte le ROB. La fenêtre d'instruction doit alors incorporer des bits pour savoir si telle entrée a émis sont instruction ou non.
===La logique d'éveil et de sélection===
Dans le cas qui va suivre, on suppose que le processeur ne peut émettre qu'une instruction à la fois. Chaque cycle, une instruction est sélectionnée et est envoyée dans le chemin de données, soit à une ALU, soit à une unité d'accès mémoire. La sélection d'une instruction est effectuée par l'unité d'émission, qui est aussi appelée le '''''scheduler'''''. Elle gère la disponibilité des opérandes et la disponibilité des unités de calcul adéquate. Le choix de l'instruction émise doit être le plus pertinent possible, pour des raisons de performances.
Pour le premier point, l'unité d'émission détermine quelles instructions sont candidates pour l'émission. Les ''instructions candidates'' sont celles dont tous les opérandes sont prêts. Le processeur contient un circuit qui détecte les instructions candidates : la '''logique d’éveil''' (''wake-up logic''). Une fois que la logique d'éveil a fait son travail, une instruction est choisie pour s’exécuter en fonction de la disponibilité des unités de calcul adéquates. Ce rôle est dévolu à un circuit qu'on appelle la '''logique de sélection''', ou ''select logic''.
[[File:Fonctionnement complet de la logique d’émission.jpg|centre|vignette|upright=1.5|Fonctionnement complet de la logique d’émission.]]
La logique de sélection doit gérer le fait que les unités de calcul du processeur ne sont pas toutes identiques. Déjà, un processeur a généralement des unités séparées pour les instructions entières et flottantes, des unités spécialisées dans les accès mémoire, parfois des unités spécialisées pour les branchements. Une instruction doit être attribuée à la bonne unité de calcul, on ne doit pas envoyer une instruction entière dans une unité de calcul flottante. De plus, parmi les unités de calcul entières, toutes ne sont pas identiques. Par exemple, il est fréquent d'avoir plusieurs unités de calcul spécialisées dans les opérations simples (additions, soustractions, opérations logiques) avec une unité pour les multiplications/divisions séparées, parfois une unité spécialisée pour les décalages/rotations, etc. Là encore, allouer la bonne instruction à la bonne ALU est complexe. Plus les unités de calcul sont hétérogènes, plus la logique de sélection est compliquée.
Les logiques d'éveil comme de sélection sont assez complexes. Comme on le verra plus bas, la logique de sélection la plus simple, qui gère une seule unité de calcul, est basée sur un encodeur à priorité couplé à d'autres circuits. Et pour gérer plusieurs ALUs, il faut mettre des encodeurs en cascade. Le résultat est que même en prenant une logique de sélection simple, elle sera assez lente et gourmande en circuits. Aussi, la logique de sélection met souvent un cycle d'horloge entier pour faire son travail, elle a son propre étage de pipeline attribué. La logique d'éveil est dans le même cas, ce qui fait qu'émettre une micro-opération demande deux cycles d'horloge.
Le défaut est que cela rajoute des cycles d'horloges entre la fenêtre d'instruction et l'ALU, ce qui complique la gestion du pipeline. Ces cycles de retard sont à prendre en compte au moment d'émettre les instructions, les ''scheduler'' doit en tenir compte pour des performances optimales. Sans tenir compte de ces cycles de retard, les instructions arrivent en retard de quelques cycles à l'ALU, cycles de retard durant lesquels l'ALU n'a pas fait de calcul et a été sous-utilisée, sans compter que le système de contournement n'est pas utilisé au mieux. Et le problème est encore plus important sur les processeur avec des stations de réservation, qui utilisent la "lecture après émission" : cela rajoute un cycle d'horloge en plus, un temps de retard en plus.
===La logique d'éveil : l'émission anticipée===
La logique d'éveil doit tenir compte du fait qu'il y a plusieurs étages entre l'émission et l'exécution sur une unité de calcul. Par exemple, un processeur a souvent entre un et à cinq étages entre la file d'attente et les unités de calcul. Il faut lire les registres, gérer le contournement, et cela peut prendre quelques cycles d'horloge. Sur le Pentium 4, on trouve 6 étages entre la fenêtre d’instruction et l'entrée de l'ALU. Si les micro-opérations sont émises quand les opérandes sont disponibles, il y aura un délai de quelques cycles avant qu'elles atteignent l'ALU. Alors qu'idéalement, on souhaiterait que la micro-opération atteigne l'ALU dès que ses opérandes sont disponibles, pour profiter des techniques de contournement. Pour cela, la logique d'éveil émet les instructions quelques cycles en avance pour qu'elles arrivent au bon moment. Il s'agit d'une technique d''''émission anticipée''', dont nous avions parlé dans le chapitre sur le contournement.
: Il faut noter que le problème se pose plus avec des fenêtres d'instruction qu'avec des stations de réservation. Les premières ont un étage en plus entre émission et ALU, vu qu'il faut lire des registres après l'émission.
L'émission anticipée demande d'émettre un signal d'éveil en avance de quelques cycles, pile au bon moment. Par exemples, si on a deux étages entre l'ALU et l'unité d'émission, le signal de réveil sera généré deux cycles avant que le résultat soit effectivement calculé. L'implémentation varie, mais deux solutions sont possibles. La première est de générer le signal d'éveil dans l'ALU, ce qui n'a de sens que pour les opérations comme les multiplications ou les divisions, qui prennent plusieurs cycles. Une autre technique génère le signal de réveil dans une structure centralisée, spécialisée dans la génération des signaux d'éveil. L'unité de composée d'un registre à décalage par registre. Imaginons qu'une instruction est émise, que cette instruction mette N cycles à s'exécuter, et qu'elle écrive dans un registre de destination. L'unité sélectionne alors le registre à décalage associé au registre de destination, et met le bit numéro N à 1. Le registre décalé d'un cran à chaque cycle. Quand le bit sortant est un 1, alors le signal de validité pour l'opérande est généré.
L'émission anticipée marche bien si l'instruction a une durée fixe, connue à l'avance. Et autant c'est le cas pour les instructions arithmétiques et logiques, autant ce n'est pas le cas pour les accès mémoire. Les accès mémoire ont une latence variable : quelques cycles pour un succès de cache, plusieurs dizaines de cycles pour un défaut de cache L1, pas loin de la centaine pour un défaut de cache L2, etc. Et cela pose des problèmes pour l'émission anticipée.
L'émission en avance pour les accès mémoire est purement spéculative : le processeur émet des instructions en supposant que la lecture entrainera un succès de cache, quitte à annuler l'instruction en cas de défaut de cache. Mais faire ainsi demande non seulement d'annuler les instructions émises en avance, mais aussi de ne pas retirer les instructions émises en avance de la fenêtre d'instruction. Elles sont émises, mais une copie de sauvegarde est conservée dans la fenêtre d'instruction. Si le processeur détecte un défaut de cache, il peut alors ré-exécuter l'instruction. S'il détecte un succès de cache, les instructions lecture-dépendante sont retirées de la fenêtre d'instruction, la copie de sauvegarde est devenue inutile et est donc jetée.
===La logique de sélection : âge ou position ?===
La logique de sélection est primordiale pour de bonnes performances. Il existe deux méthodes principales pour sélectionner l'instruction à exécuter. La première est la plus simple : elle se base sur lé numéro de l'entrée. En effet, une fenêtre d'instruction est avant tout une sorte de mémoire, un mix entre mémoire associative, mémoire RAM, et FIFO. Et chaque entrée a un numéro, une adresse mémoire. Nous parlerons de numéro dans ce qui suit, car ce sera plus simple, le terme d'adresse mémoire étant peut-être un peu exagéré dans le contexte des fenêtres d'instruction.
Toujours est-il que la fenêtre d'instruction est adressable dans le sens où on peut lui envoyer le numéro de l'entrée voulue, et la fenêtre d’instruction renvoie le contenu de l'entrée sur son port de lecture. Et c'est ce que fait la logique de sélection : elle génère le numéro de l'entrée à lire. Le numéro est alors envoyé sur l'entrée d'adresse de la fenêtre d’instruction, et elle fournit la micro-opération associée sur son port de lecture, son '''port d'émission'''.
La première méthode de sélection, donc. Elle est basée sur sur le numéro de l'entrée. Si plusieurs entrées sont éveillées, la logique de sélection prend celle avec le plus petit numéro. Elle porte le nom de '''sélection par position''', sous-entendu position dans la fenêtre d'instruction. Vous l'avez peut-être deviné, mais la logique de sélection est alors un encodeur à priorité. Le circuit de sélection ne se résume donc pas seulement à un encodeur à priorité, mais c'est le circuit central. L'avantage d'une telle méthode de sélection est qu'elle est simple à implémenter : un encodeur à priorité est un circuit connu, facile à implémenter. Mais on remarque un défaut : un encodeur est un circuit assez rapide, mais qui utilise beaucoup de transistors, beaucoup de portes logiques, surtout si on veut qu'il soit rapide. Ce qui explique que la logique de sélection ait son propre étage de pipeline rien que pour elle.
La seconde méthode s'appelle la '''sélection par âge''', au nom assez transparent. L'idée est de privilégier l'émission de l'instruction la plus ancienne. Elle demande que la fenêtre d'instruction trie les micro-opérations par ordre d'insertion dans la fenêtre d'instruction, ce qui en fait une pseudo-FIFO. Le tri est partiel, car les instructions peuvent sortir de la fenêtre d’instruction à tout moment. Compacter la fenêtre d'instruction, à savoir regrouper toutes les entrées valides est une idée, mais elle est compliquée à implémenter. Aussi, elles préfèrent encoder des informations sur l'ordre d'émission dans chaque entrée. L'encodeur à priorité doit alors travailler non pas sur des nombres entiers liés à l'ordre d'insertion dans la pseudo-FIFO. Elle est encore plus gourmande en circuits que la méthode précédente.
Les processeurs haute performance utilisent souvent des méthodes hybrides. Par exemple, les processeurs AMD de microarchitecture Bulldozer utilisaient une méthode intermédiaire. Ils utilisaient une fenêtre d'instruction couplée à un encodeur à priorité pour la sélection par position, mais complémentaient le tout avec une unité qui mémorisait l'instruction la plus ancienne dans la fenêtre d’instruction. Le mécanisme de sélection par âge était utilisé en priorité. Mais si l'instruction la plus ancienne n'était pas prête, le processeur basculait sur le mécanisme de sélection par position.
===La compaction des fenêtres d'instruction===
À chaque cycle, les instructions décodées sont ajoutées dans la fenêtre d'instruction, dans des entrées vides. Vu que les instructions quittent celle-ci dans le désordre, ces vides sont dispersés dans la fenêtre d'instruction, ce qui pose problème pour déterminer où placer les nouvelles instructions. La solution la plus triviale consiste à conserver une liste des vides, mise à jour à chaque insertion ou émission d'instruction. Une autre solution consiste à éliminer les vides en compactant la fenêtre d'instruction à chaque cycle d'horloge. Des circuits se chargent de détecter les vides et de regrouper les instructions en un unique bloc. Il faut signaler que certaines processeurs arrivent à se passer de cette étape de compactage, mais au prix de fenêtres d'instruction nettement plus complexes.
Autre problème : quand il faut choisir quelle instruction émettre, il y a toujours plusieurs candidats. Si on choisit mal, des instructions restent en attente trop longtemps parce que d'autres instructions plus jeunes leur passent devant. Pour éviter cela, les instructions les plus vielles, les plus anciennes, sont prioritaires. Pour cela, on peut utiliser une FIFO un peu spéciale pour la fenêtre d'instruction. Si les ajouts d'instruction se font dans l'ordre, les instructions ne quittent pas forcément la fenêtre d'instruction dans l'ordre imposé par une FIFO : les instructions restent triées dans leur ordre d'ajout, même s'il y a des vides entre elles. Dans ces condition, il est préférable que le compactage conserve l'ordre FIFO des instructions. Dans ces conditions, l'instruction la plus ancienne est celle qui est située à l'adresse la plus faible : le circuit de sélection peut donc être fabriqué avec des encodeurs, et est relativement simple.
==Les fenêtres d'instruction basées sur une matrice de dépendances==
L'implémentation la plus simple étend le fonctionnement d'un pipeline dynamique usuel. Rappelez-vous le chapitre sur les pipeline dynamiques. Nous avions vu que de tels pipelines émettaient une instruction par cycle, quitte à émettre des bulles de pipeline en cas de dépendance bloquante. Et il est possible d'améliorer leur unité d'émission pour qu'elle soit compatible avec l'exécution dans le désordre.
===Rappels sur le registre de réservation/disponibilité===
L'émission d'une instruction est gouvernée par un ''registre de réservation'' qui indique quels registres sont réservés par une instruction, et ceux qui sont libres. Un registre réservé est un registre qui sera écrit par une instruction en cours d'exécution, mais qui n'a pas encore écrit son résultat. Les instructions candidates doivent avoir leurs opérandes dans des registres libres. Sinon, c'est signe que leurs opérandes sont réservées en écriture, donc pas encore écrites, donc pas disponibles. Si une instruction candidate veut lire ce registre, c'est signe qu'il y a une dépendance RAW : elle veut lire un registre réservé, qui est destiné à être écrit par une instruction en vol. Si elle veut écrire, c'est signe qu'elle veut écrire dans un registre où une autre instruction veut écrire : c'est une dépendance WAW. Dans les deux cas, l'instruction n'est pas émise.
Le registre de réservation encode les registres réservés comme suit : chaque bit est associé à un registre. Le bit numéro 0 est associé au registre numéro 0, le bit numéro 1 au registre numéro 1, etc. Là, En clair, les numéros de registres réservés sont encodés en représentation non pas binaire, mais en représentation ''one-hot'' (vue au premier chapitre).
{|class="wikitable"
|+ Registre de réservation
|-
! Registre 7 !! Registre 6 !! Registre 5 !! Registre 4 !! Registre 3 !! Registre 2 !! Registre 1 !! Registre 0
|-
| 0 || 0 || 1 || 0 || 0 || 1 || 0 || 0
|}
Avant d'émettre une instruction, l'unité d'émission extrait les registres opérande/destination et les convertis dans la même représentation que le registre de réservation, à savoir en représentation ''one-hot''. Le circuit de traduction binaire vers ''one-hot'' est, pour rappel, un simple décodeur. Puis, on fait un OU logique entre les sorties des décodeurs. Le résultat est un masque qui indique quels registres sont lus par l'instruction, encodé en représentation ''one-hot''. Appelons-le le '''masque d'opérandes'''. Le masque est alors comparé au registre de réservation pour vérifier si l'instruction peut être émise.
[[File:Unité d'émission simple, dans l'ordre.png|centre|vignette|upright=2|Unité d'émission simple, dans l'ordre]]
Si l'émission est autorisée, le registres de réservation est là aussi mis à jour. Le registre de réservation est aussi mis à jour dès qu'une instruction enregistre son résultat dans les registres, ou alors dès qu'il est disponible pour le contournement. Le bit associé au registre repasse alors à 0 (ou à 1). Sans contournement, la mise à jour se fait alors à la toute fin de l'instruction, quand elle se termine, lors de la dernière étape d'enregistrement, à la fin de son pipeline. Avec, elle se fait quand l'instruction quitte l'unité de calcul.
===L'extension à l'exécution dans le désordre : l'éveil par matrice de bits===
La différence entre un pipeline dynamique et l'exécution dans le désordre est que l'on passe d'une instruction à émettre à plusieurs. Plusieurs instructions sont mises en attente dans une fenêtre d’instruction, et y attendent leur tour. L'idée est alors d'envoyer le registre de disponibilité à toutes les entrées, à toutes les instructions en attente. Ainsi, on sait quelles sont les instructions qui peuvent s'exécuter et celles qui ne le peuvent pas. L'implémentation est assez simple, elle ressemble beaucoup à l'implémentation d'un pipeline dynamique simple, si ce n'est que des circuits sont dupliqués.
L'implémentation utilise des fenêtres d'instruction légèrement différentes de celles introduites plus haut. Elles n'utilisent notamment pas de bits de disponibilité, mais autre chose. Lorsqu'une instruction est ajoutée à la fenêtre d'instruction, le masque de disponibilité des opérandes est stocké dans la fenêtre d'instruction. A chaque cycle, le registre de disponibilité est envoyée à toutes les entrées, et est comparé avec tous les masques de disponibilité des opérandes. Si une comparaison renvoie un 1, alors l'instruction de l'entrée est une instruction candidate à l'émission.
[[File:Unité d'émission dans le désordre basée sur un registre de disponibilité.png|centre|vignette|upright=2.5|Unité d'émission dans le désordre basée sur un registre de disponibilité]]
L'ensemble est implémenté avec l'aide d'une '''matrice de bits''', où chaque ligne correspond à une entrée et chaque colonne à un registre. À chaque croisement entre une ligne et une colonne, on trouve un bit. Si le bit de la ligne n et de la colonne m est à 1, cela veut dire : l'instruction dans l'entrée n a besoin de lire le registre m. S’il est à 0, alors cela veut dire que l'instruction stockée dans la ligne n n'a pas besoin de la donnée dans le registre m.
[[File:Planificateur à matrice de bits.jpg|centre|vignette|upright=2|Planificateur à matrice de bits.]]
Lorsqu'une instruction réserve une entrée, elle initialise la ligne en fonction des registres qu'elle souhaite lire. Pour vérifier si une instruction a ses opérandes prêts, le processeur compare le registre de disponibilité à la ligne associée à l'instruction/entrée. A chaque intersection ligne-colonne, se trouve un comparateur de 1 bit, qui détecte si le registre est demandé et disponible. Les résultats de cette porte sont ensuite envoyés à une porte ET, qui fait le gros du travail.
[[File:Logique de détection de la disponibilité d'une instruction.jpg|centre|vignette|upright=2|Logique de détection de la disponibilité d'une instruction.]]
==Les fenêtres d'instruction basées sur une mémoire associative==
Les processeurs modernes utilisent une mémoire associative pour la fenêtre d'instruction. Les mémoires associatives ont été abordées il y a quelques chapitres, mais faisons quelques rappels. Les mémoires associatives servent à accélérer la recherche de données dans un ensemble. Or, une fenêtre d'instruction vérifie régulièrement si chaque entrée est prête. Il s'agit d'un processus de recherche d'une instruction qui respecte une condition bien précise dans la mémoire, ce qui fait que les mémoires associatives sont donc tout indiquées.
Comme vous le savez, les signaux de commande d'une instruction sont propagés avec celle-ci dans le pipeline, le nom du registre de destination ne faisant pas exception. Le nom de registre est envoyé à la fenêtre d'instruction lors de l'écriture du résultat dans les registres. S'il y a correspondance, l'opérande est disponible et son bit de disponibilité est mis à 1. On peut adapter cette méthode pour tenir compte du contournement assez simplement.
[[File:Détection des dépendances par propagation du registre de destination.png|centre|vignette|upright=2|Détection des dépendances par propagation du registre de destination.]]
En conséquence, le circuit de détection des dépendances est constitué d'un grand nombre de comparateurs : un par champ « nom de registre » dans chaque entrée. La logique d'éveil/''wake up'' regroupe tous les comparateurs, la logique de sélection est un circuit situé en-dehors de la mémoire associative. Les entrées sont dans la mémoire associative elle-même.
[[File:Gestion des bits de validité.png|centre|vignette|upright=2|Gestion des bits de validité.]]
L'implémentation la plus simple est celle du schéma précédent, où on utilise une mémoire associative unique. Une version plus élaborée sépare la fenêtre d'instruction en deux parties : une mémoire qui mémorise les registres des opérandes, une autre mémoire pour le reste. La mémoire pour les registres opérandes est la mémoire associative proprement dite, c'est elle qui est associées aux comparateurs, à la logique de ''wake-up''/réveil, etc. Par contre, l'autre mémoire est une mémoire RAM simplifiée, qui mémorise les informations qui n'ont pas besoin des comparateurs. L'opcode de l'instruction est là-dedans, par exemple.
===Les optimisations de la consommation d'énergie : la désactivation des portions inutilisées===
Le problème avec des fenêtres d'instruction de ce type est leur forte consommation énergétique, ainsi que le grand nombre de portes logiques utilisées, qui deviennent prohibitif pour des fenêtres d'instruction un peu grosses. Pour résoudre ce problème, certains ont optimisé les comparateurs. D'autres ont tenté de profiter du fait que la majorité des bits dans les entrées sont composés de zéros. Bref, les optimisations purement matérielles sont légion.
Une première solution est de désactiver les entrées qui sont inutilisées, vides, sans instruction. Pour cela, on fait précéder les comparateurs par un circuit qui les connecte ou déconnecte des lignes de bit. Le circuit est commandé par le bit ''empty'' qui indique si une entrée est vide ou non. L'avantage est que la consommation d'énergie de la fenêtre d'instruction devient alors proportionnelle au nombre d'entrées non-vides, et non au nombre d'entrées total. Du moins, en apparence, les entrées non-utilisées doivent quand même être alimentées pour fonctionner, mais l'activité de comparaison disparait dans les entrées vides. Il est possible d'étendre cette technique aux entrées dont les opérandes sont prêtes. Les gains sont généralement assez bons, avec une réduction de consommation variant entre 30 et 50%, avec une implémentation très simple et très économe en circuits.
Une autre solution, assez simple à mettre en place, consiste à désactiver une partie de la fenêtre d'instruction sin elle est inutilisée. Pour cela, il faut segmenter la fenêtre d'instruction avec la technique du ''wire partitionning''. Pour rappel, une fenêtre d'instruction est une mémoire associative. Et comme toute mémoire associative, elle contient des fils, les lignes de bit, qui relient les entrées/sorties à toutes les cellules mémoire. Plus une ligne de bit est longue, plus elle consomme d'énergie et plus le temps de lecture/écriture est long.
L'idée est de segmenter les lignes de bits en plaçant des répéteurs qui recopient la tension d'un segment au suivant. Les répéteurs sont des tampons trois-états, ce qui permet de déconnecter les portions inutilisées de la ligne de bit. Ce faisant, la fenêtre d'instruction est découpée en segments, qui peuvent être désactivés si besoin. Si il y a peu d'instructions chargées dans la fenêtre d'instruction, les portions inutilisées sont désactivées. La technique marche d'autan mieux si les instructions sont compactées au début de la fenêtre d'instruction.
[[File:Wire partitionning.png|centre|vignette|upright=2|Wire partitionning]]
Tout le problème est de savoir quelles portions désactiver. La technique est assez simple si la fenêtre d'instruction est compactée, à savoir si toutes les instructions sont régulièrement regroupées au début de la mémoire CAM. Mais même avec cette optimisation, la décision de désactiver une portion de la fenêtre d'instruction n'est pas à prendre à la légère. Il ne faut pas désactiver une portion contenant des instructions pouvant être émise sous peu. La décision dépend de paramètres variés : proportion d'entrée vides, quantité d'entrées récemment activées/désactivées récemment, présences d'entrées avec un ''match'' d'opérandes récent, etc.
Il est important de préciser que les techniques en question fonctionnent aussi sur les autres types de fenêtre d'instruction, qui ne sont pas basées sur des mémoires associatives. Nous allons voir celles-ci dans la suite du chapitre, mais gardez à l'esprit que ces optimisations, à savoir désactiver les entrées vides/prêts et partitionner la fenêtre d'instruction, sont des optimisations générales qui marchent avec presque tout.
===L'ajout d'une FIFO pour les instructions déjà prêtes à l'émission===
D'autres techniques permettent de réduire aussi bien le cout en circuits que la consommation d'énergie de la fenêtre d'instruction. L'idée est d'utiliser une mémoire associative quand elle est réellement nécessaire, mais d'utiliser une fenêtre d'instruction "normale" dès que possible.
La méthode la plus simple tient compte du fait que certaines instructions ont déjà toutes leurs opérandes de disponibles à l'émission, mais doivent quand même être mises en attente, parce que l'ALU adéquate est occupée, parce que des dépendances WAW/WAR sont encore là, ou pour tout autre raison. Dans ce cas, elles sont mises en attente non pas dans la fenêtre d'instruction, mais dans une mémoire FIFO toute simple. L'idée est qu'au lieu d'avoir une énorme mémoire associative pour la fenêtre d'instruction, on déplace quelques entrées dans une petite FIFO annexe. Le résultat est un gain en terme d'énergie et de circuits, au prix d'une perte de performance mineure. La perte de performance en question se manifeste si aucune instruction n'a ses opérandes de prêtes à l'émission : le programme n'utilise pas la FIFO et voit alors une fenêtre d'instruction plus petite.
Une amélioration de cette technique tient compte du fait que certaines instructions sont dans un cas intermédiaire. Elles ont déjà une opérande de prête lors de l'émission, mais pas l'autre. Le nombre d'opérandes varie suivant l’instruction : certaines n'ont besoin que d'un seul opérande. De plus, il arrive qu'une opération tout juste décodée ait déjà un ou plusieurs de ses opérandes de prêts. Cette constatation permet d'éviter d'utiliser des comparateurs pour les opérandes déjà prêts. Par exemple, on peut parfaitement utiliser trois fenêtres d'instruction :
* une pour les instructions dont tous les opérandes sont prêts, mais qui sont quand même mises en attente, sans comparateur ;
* une pour les instructions dont un seul opérande manque, qui n'utilise qu'un comparateur par entrée ;
* une pour les instructions dont deux opérandes manquent à l'appel, qui utilise deux comparateurs par entrée.
C'est beaucoup plus économique que d'utiliser une seule grosse fenêtre d'instruction qui contiendrait autant d'entrées que les trois précédentes réunies, avec deux comparateurs par entrée. Certains sont même allés plus loin, et ont proposé de supprimer la fenêtre d'instruction avec deux opérandes par entrée. Les instructions dont deux opérandes sont inconnus au décodage sont stockées dans la fenêtre d'instruction pour instructions avec un comparateur par entrée. Un circuit de prédiction se charge alors de prédire l'opérande manquant, cette prédiction étant vérifiée plus loin dans le pipeline. Il serait cependant étonnant qu'une telle proposition ait donné lieu à la moindre implémentation réelle.
===Les optimisations de la consommation d'énergie avancées===
D'autres chercheurs ont conservé une fenêtre d'instruction unique, avec autant de comparateurs par entrée qu'il y a d'opérandes possibles par instruction. Simplement, le processus de détection des opérandes prêts est légèrement ralenti : on ne vérifie qu'un opérande par cycle. Pour vérifier les deux opérandes d'une entrée, on doit attendre deux cycles.
Enfin, certains chercheurs ont proposé des fenêtres d'instruction segmentées. Les instructions circulent à chaque cycle d'un segment vers le suivant : en conséquence, certains segments conservent les instructions les plus anciennes, un autre les instructions les plus jeunes, etc. Seul le denier segment, celui qui contient les instructions les plus vielles, peut émettre une instruction : la détection des opérandes se fait seulement dans le dernier segment, qui contient les instructions les plus anciennes. On économise ainsi beaucoup de comparateurs.
Certains chercheurs ont tenté de pipeliner l'étape de sélection des opérandes, ainsi que l'étage d'arbitrage. Mais en faisant cela, il faut plusieurs cycles pour détecter qu'une instruction a ses opérandes prêts, ce qui pose problème face à de nombreuses dépendances RAW. Pour éviter de trop perdre en performances, certains chercheurs ont décidé d'utiliser des techniques pour prédire quelles seront les instructions dont les opérandes seront bientôt prêts. Si détecter qu'une instruction est prête prend n cycles, le processeur devra tenter de prédire la future disponibilité des opérandes n cycles en avance pour obtenir des performances optimales.
===Remplacer la mémoire associative par une RAM===
Une autre optimisation possible est de remplacer la fenêtre d'instruction par une mémoire RAM, dont chaque mot mémoire correspondrait à une entrée. Une telle optimisation permet en théorie de se passer des comparateurs associés à chaque entrée, mais au prix de l'ajout de circuits annexes potentiellement couteux.
La première de ces techniques, la '''recherche directe par étiquette''' (direct tag search) fut créée par Weiss et son collègue Smith. Ils cherchaient à améliorer l'algorithme de Tomasulo, un algorithme qu'on expliquera dans quelques chapitres. Rappelons que chaque instruction produit un résultat, qui devra être rapatrié dans une ou plusieurs entrées de la fenêtre d'instruction. L'optimisation de la recherche directe par étiquette fonctionne dans le cas où le résultat de l'instruction n'est utilisé que par une seule entrée, et pas plusieurs.
Le principe est simple : chaque champ « opérande » d'une entrée est adressable via le nom de registre de cette opérande. Pour faire le lien entre entrée et nom de registre, la recherche directe par étiquette ajoute une table de correspondances matérielle, une mémoire RAM qui mémorise les adresses des champs « opérande » des entrées. Les adresses envoyées dans cette mémoire sont les noms de registres des résultats. Lors de l'émission, la table de correspondances est mise à jour. Les numéros des champs « opérande » réservés lors de l'émission sont mémorisés dans les mots mémoire qui correspondent aux deux registres sources. Toutefois, une petite vérification est faite lors de l'émission : si il y a déjà une correspondance dans la table pour un registre source, alors une autre instruction compte lire ce registre. Pour éviter d'écraser les données de l'instruction précédente dans la table de correspondances, l'émission de l'instruction est bloquée.
[[File:Recherche directe par étiquette.png|centre|vignette|upright=2|Recherche directe par étiquette.]]
==Les fenêtres d’instruction avec préplanification==
Avec la '''préplanification''' (''prescheduling''), la fenêtre d’instruction est composée de mémoires FIFO dans lesquelles les instructions sont triées dans l'ordre d'émission. Il n'y a pas de logique d’émission proprement dite, celle-ci se bornant à vérifier si l'instruction située au début de la FIFO peut s’exécuter. Par contre, le préplanificateur détecte les dépendances et en déduit où insérer les instructions dans le tampon d'émission, à l'endroit le plus adéquat.
[[File:Préplanification.jpg|centre|vignette|upright=2|Préplanification.]]
===L'usage de FIFO multiples===
La technique de préplanification la plus simple utilise plusieurs FIFO. Quand une instruction a une dépendance avec une autre instruction, les deux sont placées dans la même FIFO. Pour cela, le préplanificateur détecte les chaines d'instructions dépendantes, et place les instructions dans la FIFO adéquate.
A chaque cycle, l'unité d'émission lit une micro-opération dans chaque FIFO et vérifie si elle peut l'émettre. Si la micro-opération n'a pas ses opérandes disponibles, ou qu'une dépendance structurelle survient, alors la FIFO est bloquée. La micro-opération attend que la dépendance soit résolue, bloquant toutes les instructions précédentes. Mais les autres FIFO ne sont pas bloquées, laissant de la marge de manœuvre.
[[File:Préplanification par FIFO multiples.png|centre|vignette|upright=1.5|Préplanification par FIFO multiples.]]
===La technique du tampon trié===
La technique du '''tampon trié''' trie les instructions selon le temps d'attente avant leur exécution. Les instructions qui sont censées s’exécuter bientôt sont insérées au tout début de la FIFO, tandis que les instructions qui s’exécuteront dans un long moment sont insérées vers la fin. Là encore, l'unité d'émission lit une micro-opération dans la FIFO, et détermine si elle peut être émise. Si une dépendance survient, l'émission est retardée, et la FIFO est bloquée.
[[File:Préplanification par tampon trié.png|centre|vignette|upright=1.5|Préplanification par tampon trié.]]
La technique a cependant quelques problèmes assez importants. Le premier problème est que la FIFO est bloquée lorsqu'une micro-opération ne peut être émise. Le second problème est que le temps avant exécution n'est pas toujours connu, notamment pour les instructions d'accès mémoire. Et cela se répercute sur les instructions dépendantes de celles-ci. Pour résoudre ce genre de problèmes, l'usage d'un pipeline à ''replay'' est possible, et est même l'une des meilleures solution possible, malgré ses défauts.
L'idée est que les lecture sont pré-planifiées en supposant qu'elles font un succès de cache L1. Si la prédiction est fausse, la lecture est ré-exécutée plusieurs cycles plus tard, en supposant qu'elle fait un succès dans le cache L2, et ainsi de suite. Elle est alors ré-insérée dans la FIFO, elle est pré-planifiée une seconde fois. Il en est de même si une micro-opération ne peut pas être émise : il suffit de la réinsérer dans la FIFO au bon endroit, en attendant que ses dépendances soient résolus. Ainsi, l'instruction fautive ne bloque pas la fenêtre d’instruction, et retente sa chance autant de fois qu'il le faut.
[[File:Préplanification par fenêtre d’instruction scorebarodée.png|centre|vignette|upright=1.5|Préplanification par fenêtre d’instruction scorebarodée.]]
Une autre solution utilise deux fenêtres d’instruction : une FIFO gérée par la préplanification, et une vraie fenêtre d'instruction pour les instructions dont le temps d'attente ne peut pas être déterminé. Il est possible de mettre cette dernière avant la préplanification, afin de gérer les temps d'attente des instructions d'accès mémoire. Quand le temps d'attente d'une lecture devient connu, ses instructions dépendantes sont gérées avec préplanification.
[[File:Préplanification avec fenêtre d’instruction.png|centre|vignette|upright=2.5|Préplanification avec fenêtre d’instruction.]]
===Les fenêtres d'instruction avec allocation temporelle statique===
L''''allocation temporelle statique''' correspond à une technique utilisée sur les processeurs Cuzco de l'entreprise Condor. La technique en question a été présenté en 2025, mais des idées similaires ont été présentées dans la littérature académique. Elle ressemble beaucoup aux techniques de pré-planification, avec cependant quelques différences.
L'idée est simple : le processeur dispose de plusieurs fenêtres d'instruction, qui mettent en attente les micro-opérations. Lors de l'étape de renommage de registres, le processeur détermine dans combien de cycles d'horloge la micro-opération sera prête pour exécution. La micro-opération est alors mise en attente durant ce nombre de cycles, dans les fenêtres d'instruction, puis est émise une fois ce nombre de cycles écoulés. Ce faisant, les fenêtres d'instruction n'ont pas besoin de détecter la disponibilité des opérandes à chaque cycle, pour chaque micro-opération en attente. Le ''timing'' de l'émission des micro-opérations est décidé à l'avance.
L'implémentation exacte n'est pas connue, mais plusieurs solutions sont imaginables. Par exemple, la fenêtre d'instruction peut simplement être composée de plusieurs mémoires de petite taille, chacune contenant les micro-opérations destinées à être exécutées dans N cycles, N étant différent pour chaque mémoire. Une autre solution, plus réaliste, utilise une fenêtre d'instruction dans laquelle la micro-opération est couplée à des champs pour les opérandes, et un champ compteur. Le champ compteur est initialisé avec le nombre de cycles à attendre, et est décrémenté à chaque cycle d'horloge. Quand il atteint zéro, la micro-opération est émise.
Vous remarquerez que la méthode ne marche que si toutes les micro-opérations prennent un nombre fixe de cycles d'horloge. Et autant c'est le cas pour les micro-opérations arithmétiques ou logiques, autant les accès mémoire ne rentrent pas dans ce cadre. En théorie, on ne sait pas combien de temps prendra un accès mémoire. Et cette incertitude se répercute sur les instructions dépendantes d'un accès mémoire.
Pour éviter cela, le processeur réutilise un pipeline à ''replay'' tel que vu dans le chapitre précédent. L'unité de renommage de registre suppose que tout accès mémoire fait un succès de cache L1, et décide des ''timings'' d'émission sous cette hypothèse. Si une lecture subit un défaut de cache, elle est ré-exécutée, de même que toutes les instructions dépendantes. Pour cela, le registre de destination de la lecture est marqué comme invalide, grâce à un bit spécifique attaché au registre. Toute instruction qui lit une opérande invalide est elle aussi ré-executée de zéro.
Pour simplifier, l'unité d'émission ressemble à une unité d'émission dans l'ordre, avec un registre de disponibilité, qu'on aurait amélioré pour aller au-delà de la simple détection des dépendances d'instruction. Pour déterminer les ''timings'' d'émission, à savoir quand émettre une micro-opération, le processeur utilise une unité d'émission à deux étages. Le premier étage est appelé le ''register scoreboard'', le second étage est appelé la ''Time Ressource Matrix''. Les deux étages communiquent entre eux, comme on va le voir, et leurs noms donnent une idée de ce qu'ils font. Le premier étage gère les dépendances de registres, le second gère les dépendances structurelles.
Le premier étage est un registre de disponibilité amélioré, qui gère les dépendances de registre. Cet étage sait quand tel registre sera écrit, et donc que l'opérande écrite dedans sera disponible à partir de tel cycle d'horloge. Il le sait car l'étage suivant l'aura prévenu, mais laissons cela de côté pour le moment. Quand une micro-opération rentre dans cet étage, il vérifie les dépendances avec les registres et les micro-opérations antérieures. Vu qu'il sait quand les registres seront disponibles, il peut déterminer quand l'instruction pourra s'exécuter. Par exemple, si une micro-opération lit les deux registres R7 et R15, qui sont disponibles respectivement dans 5 et 7 cycles, le premier étage sait que la micro-opération devra attendre 7 cycles.
Mais tout cela ne suffit pas à déterminer quand émettre une micro-opération. En effet, il fait aussi gérer les dépendances structurelles, ce qui est le boulot du second étage. Le second étage utilise pour cela une ''Time Ressource Matrix'', qui mémorise l'occupation de diverses "ressources", pour les 256 cycles d'horloge à venir. Les ressources en question sont les ports de lecture/écriture du banc de registre, les ALUs utilisées, si la micro-opération utilise l'unité mémoire, etc. Le second étage prend en entrée une micro-opération, détermine quelles "ressources" elle utilise, puis regarde leur disponibilité. Elle détecte alors les dépendances structurelles et détermine alors quand peut s'exécuter la micro-opération. Elle peut retarder l'émission de quelques cycles, si une dépendance structurelle est détectée.
Une fois sortie du second étage, on sait quand la micro-opération va être émise, à quel cycle précisément. Et vu que la durée de l'instruction est fixe, on sait quand cette micro-opération va se terminer, quand son résultat sera disponible. Cela permet de mettre à jour la ''Time Ressource Matrix'', pour préciser que le port d'écriture sera occupé à tel moment. De plus, cette information est transmise au premier étage, qui sait alors quand est enregistré le résultat dans le registre de destination. C'est comme cela que le premier étage sait quand un registre est disponible : le second étage le prévient, quand il émet une instruction !
La ''Time Ressource Matrix''mémorise l'occupation des "ressources" pour les 256 cycles d'horloge à venir. Cependant, elle ne vérifie pas les dépendances pour les 256 cycles suivants. Quand elle reçoit une micro-opération, elle teste les dépendances structurelles pour seulement les 8 prochains cycles maximum. Si la micro-opération ne peut pas être émise pendant ces 8 cycles, le pipeline est bloqué, un ''pipeline stall'' est émis. Pour être plus précis, vu que le processeur peut décoder 8 micro-opérations en même temps, cela permet de ne consulter que 64 cycles d'horloge sur les 256.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les premiers processeurs Intel
| prevText=Les premiers processeurs Intel
| next=Le renommage de registres
| nextText=Le renommage de registres
}}
</noinclude>
luq5td604txixhk0585xn9b6q5wa41m
773350
773349
2026-09-27T20:32:33Z
Mewtow
31375
/* Un cas extrême : les fenêtres d'instruction totalement décentralisées */
773350
wikitext
text/x-wiki
Dans les chapitres précédents, nous avons parlé des techniques d'émission dans l'ordre. L'idée est d’exécuter une nouvelle instruction à chaque cycle, dans des unités de calcul séparées. Les instructions sont émises consécutivement et l'unité d'émission bloque l'émission en cas de dépendance, mais les instructions s’exécutent en parallèle dans des unités de calcul séparées. De plus, elles peuvent se finir dans le désordre, en absence de dépendances, si les exceptions sont imprécises. Le point important est qu'on doit exécuter des instructions indépendantes dans des unités de calcul séparées.
Les techniques d'exécution dans le désordre que nous allons voir dans ce chapitre sont dites à '''émission dans le désordre'''. Elles se distinguent des précédentes sur un point précis : l'unité d'émission bloque l'émission d'une instruction, elle seule est bloquée, les instructions suivantes peuvent poursuivre. L'émission des instructions peut donc se faire dans le désordre, dans le sens où une instruction peut être émise alors qu'une instruction précédente ne l'est pas encore.
L'avantage de l'émission dans le désordre est qu'une instruction s'exécute dès que ses opérandes sont disponibles. Le processeur incorpore de quoi déterminer quand un résultat est disponible, de quoi savoir quand un opérande est prête. Par prête, on veut dire présente soit dans le réseau de contournement, soit enregistrée dans les registres. Dès qu'une instruction a ses opérandes disponibles, elle est émise et exécutée. C'est là la seule contrainte : les dépendances WAR, WAW et autres sont gérées par un ROB ou toute autre technique d'exception précise, seules les dépendances RAW limitent l'exécution.
: Dans la suite, nous utiliserons parfois l'abréviation OOO pour parler d'exécution dans le désordre. L'abréviation est celle de ''Out Of Order Excution''.
Mais avant de poursuivre, précisons une chose importante : l'exécution dans le désordre traite les accès mémoire à part. La majorité des processeurs OOO assez anciens exécutent les accès mémoire dans l'ordre, seules les instructions entières/flottantes/branchements sont exécutées dans le désordre. L'exécution dans le désordre des accès mémoire est courante sur les processeurs récents, mais c'est l'unité d'accès mémoire qui se charge de changer l'ordre des accès mémoire toute seule dans son coin. Nous allons volontairement mettre de côté la gestion des accès mémoire dans ce chapitre.
==Les fenêtres d’instruction : généralités==
Pour implémenter l'exécution dans le désordre, il faut ajouter une sorte de mémoire qui met en attente les instructions bloquées, à émettre. Les instructions attendent dans cette mémoire le temps que leurs opérandes soient disponibles. De plus, il faut un circuit capable de gérer les dépendances RAW. L'unité d'émission qui bloquait les instructions, ne bloque plus rien, car elle faisait office de dépendance structurelle qui est éliminée.
[[File:File de micro-opération.png|vignette|upright=1|File de micro-opérations]]
Les processeurs sans exécution dans le désordre ont une unité d'émission qui bloque tout le pipeline dès qu'une instruction ne peut pas être émise. Pour limiter l'impact de ce blocage, quelques rares processeurs incorporent une mémoires FIFO appelée la '''file d'instruction''', aussi appelée '''file de micro-opération'''. Elles permettent d'émettre des instructions dans l'ordre, à savoir qu'elles quittent la file d'instruction dans l'ordre d'ajout, dans l'ordre de décodage. Le schéma ci-dessous illustre une telle file d'attente couplée à un ''scoreboard''.
Les techniques modernes d'OOO utilisent aussi des files de micro-opération améliorées, comme on le verra plus bas. De telles files de micro-opération permettent d'émettre des instructions dans le désordre, ce qui corrige le problème mentionné plus haut avec le ''scoreboard''. Si une instruction bloque le pipeline, les instructions suivantes peuvent être émises.
Il existe dans les grandes lignes 4 grandes techniques d'exécution dans le désordre. Elles peuvent se classer sur deux critères : est-ce que les instructions sont émises dans l'ordre, et combien il y a de files de micro-opération.
{|class="wikitable"
|-
!
! Emission dans l'ordre
! Emission dans le désordre
|-
! Une file de micro-opération
| ''Scoreboard''
| Fenêtre d'instruction centralisée
|-
! Plusieurs files de micro-opération
| Plusieurs FIFOs, plusieurs ''Scoreboard'' indépendants
| Fenêtre d'instruction décentralisée
|}
===L'usage de plusieurs files d'instruction===
La première méthode évoluée d'OOO utilise plusieurs files de micro-opération. L'idée est qu'une fois décodée, les instructions sont accumulées dans des files de micro-opération différentes. Il y a typiquement une file de micro-opération pour les opérations entières, une autre pour les opérations flottantes, une autre pour les accès mémoire. D'autres processeurs utilisent une file pour les accès mémoire et une autre pour les autres instructions, comme le faisait le Pentium 4 d'Intel.
Les files de micro-opération sont des mémoires FIFO, ce qui fait qu'elles conservent l'ordre des instructions. Et chaque FIFO est couplée à un ''scoreboard'' qui vérifie les dépendances à chaque cycle. La présence de ''scoreboard'' nous dit que l'intérêt n'est pas un mécanisme d'émission plus compliqué, mais que la technique se repose sur le fait d'avoir plusieurs files de micro-opération. L'intérêt d'avoir plusieurs FIFOs est que si une dépendance bloque une FIFO, les autres peuvent continuer d'émettre des instructions.
Les files de micro-opération émettent leurs instructions de manière indépendante, ou presque. Elles ne savent pas si telle instruction dans une autre file doit passer avant ou après l'instruction qu'elles émettent. Par contre, les unités d'émission communiquent entre eux pour gérer la disponibilité des registres. Quand un registre est lu ou écrit par une instruction, les autres files d'instruction doivent être prévenues et mettre à jour leurs unités d'émission. La logique d'émission est donc plus complexe que prévu. Le processeur Pentium 4 avait deux FIFOs séparées : une pour les instructions d'accès mémoire et une autre pour les autres instructions. Si jamais une instruction mémoire bloquait le pipeline, l'autre FIFO pouvait continuer à exécuter des instructions entières indépendante de la lecture bloquée.
Les FIFOs sont simples à implémenter et ont un cout en circuit modéré. Par contre, le gain en performances est assez limité avec cette méthode, surtout comparé aux méthodes qui vont suivre. Le problème est qu'il est facile de se retrouver avec toutes les files de micro-opération bloquées. Quand l'une est bloquée, les autres tendent à se vider rapidement, particulièrement quand c'est la file pour les accès mémoire qui se bloque. Les techniques qui vont suivre font totalement disparaitre ce genre de blocage.
===La fenêtre d'instruction centralisée===
Les processeurs OOO modernes utilisent une file d'attente modifiée, où les instructions sont insérées dans l'ordre, mais peuvent sortir dans le désordre, être émises dans le désordre. La file d'attente en question est appelée la '''fenêtre d'instruction'''. Les instructions décodées sont accumulées dans la fenêtre d'instruction, où elles attendent que leurs opérandes soient disponibles. La fenêtre d'instruction sert donc de file d'attente. A chaque cycle, l'unité d'émission consulte la fenêtre d'instruction pour voir quelles instructions peuvent s'exécuter, quelles instructions ont leur opérandes de disponibles. Elle choisit une instruction exécutable et l'envoie aux ALU. La fenêtre d'instruction doit spécialement être conçue pour, c'est une sorte de mémoire associative très complexe.
[[File:OOO Issue.png|centre|vignette|upright=2.5|Exécution dans le désordre avec une fenêtre d'instruction]]
Le cas le plus simple n'utilise qu'une seule fenêtre d'instruction. Avec elle, l'unité d'émission, détecte les dépendances et répartit les instructions sur les unités de calcul. Les instructions décodées sont ajoutées en parallèle dans la fenêtre d'instruction, et le ROB. La raison est que les instructions quittent la fenêtre d'instruction dans le désordre, l'ordre des instructions est perdu après l'unité de décodage/renommage de registre. Aussi, pour remplir le ROB, la seule opportunité est en sortie de l'unité de décodage. Dans le cas où une instruction est décodée en plusieurs micro-opérations, elles sont toutes insérées en même temps dans la fenêtre d'instruction et le ROB.
[[File:Fenêtre d'instruction.png|centre|vignette|upright=2|Fenêtre d'instruction.]]
Les micro-opérations mémoire sont à part des autres, pour diverses raisons. Une de ces raisons est que les dépendances de données sont classées en deux types : les dépendances de registre et les dépendances d'adresse. Et les deux sont fondamentalement différentes. Autant on peut détecter les dépendances de registre lors de l'émission, autant c'est plus compliqué avec les dépendances d'adresse. De plus, rappelons que si les lectures peuvent s'exécuter dans le désordre, les écritures doivent s'exécuter dans l'ordre, ou du moins en donner l'illusion. Pour cela, le processeur remet les écritures dans l'ordre avec une sorte de mini-ROB intégré à l'unité mémoire, appelée la file d'écriture. Elle complémente le ROB, qui lui ne gére que les écritures dans les registres, mais les deux collaborent.
Les processeurs grand public anciens n'autorisaient pas d'exécution dans le désordre des accès mémoire. Pour cela, l'unité mémoire est précédée par une file de micro-opérations, pour forcer l'exécution dans l'ordre des accès mémoire. Les processeurs modernes ajoutent des techniques d'exécution dans le désordre des accès mémoire, mais nous les verrons dans un chapitre dédié. Dans les deux cas, les micro-opérations mémoire doivent être envoyées à l'unité mémoire dans l'ordre du programme, dans l'ordre de décodage. Dans ce chapitre, on suppose que l'unité mémoire est précédée d'une file de micro-opération mémoire dédiée, rien que pour elle. Elle est à part du reste.
[[File:Processeur avec émission dans l'ordre des accès mémoire.png|centre|vignette|upright=2|Processeur avec émission dans l'ordre des accès mémoire]]
===Les fenêtres d'instruction décentralisées===
Une autre solution utilise plusieurs fenêtres d'instruction. Pour faire la différence avec l'usage d'une fenêtre d'instruction unique, nous allons parler de fenêtre d'instruction centralisée s'il n'y en a qu'une dans le processeur, de '''fenêtre d'instruction décentralisée''' s'il y en a plusieurs. Dans le cas général, chaque fenêtre d'instruction est associée à un type précis d'opération/instruction. La tendance moderne est d'utiliser une fenêtre d'instruction pour les instructions de calcul entières, une autre pour les flottantes. Il s'agit d'un choix simple et efficace, mais quelques processeurs font autrement, pour répondre à des contraintes très diverses.
L'usage de fenêtres décentralisées simplifie la répartition des instructions sur les différentes unités de calcul. Par exemple, utiliser des fenêtres séparées pour les ALU et les FPU facilite la répartition des instructions de calcul sur les ALU. Une fenêtre centralisée demande de faire la différence entre instruction entière/flottante, puis de choisir sur quelle unité de calcul entière/flottante utiliser. Une fenêtre décentralisée fait les deux choix séparément : d'abord on envoie l'instruction dans la bonne fenêtre suivant si c'est une instruction flottante ou entière, et ensuite on gère la disponibilité des ALUs/opérandes. Et on peut adapter la même idée en séparant les unités d'accès mémoire des ALU entières, etc.
Par contre, un défaut est que certaines fenêtres d'instruction peuvent être sous-utilisées. Par exemple, la station de réservation flottante est inutilisée si le programme n'exécute pas d'opérations flottantes. Ou encore, les fenêtres pour les accès mémoire peuvent être sous-utilisées, si le programme effectue peu d'accès mémoire relativement aux autres instructions. En fait, tout dépend de comment sont calibrées les fenêtres d'instruction et de la répartition des instructions dans le programme entre opérations entières, flottantes, mémoire. Par exemple, si un programma de deux fenêtres d'instructions identiques, une pour les calculs entiers et une autre pour les calculs flottants, elles ne seront utilisées à la perfection que si la moitié des instructions du programme fait des calculs flottants et l'autre des calculs entiers. Toute déviation par rapport à cette répartition idéale risque de laisser des entrées vides dans une fenêtre d’instruction.
Un point important est que l'émission est découpée en deux étages avec des fenêtres d'instruction décentralisées. La première étape envoie l'instruction décodée vers la fenêtre d'instruction adéquate, la seconde gère l'émission proprement dit. La première est l'étage de ''dispatch'' qui envoie l'instruction dans la fenêtre d’instruction adéquate, selon que c'est une instruction flottante, entière, autre. La seconde est l'étage de ''scheduling'', qui gère la disponibilité des opérandes et des unités de calcul. En clair, on trie les instructions suivant leur type, suivant le type d'unité de calcul adéquate, puis on l'émet proprement dit. Faire ainsi a plusieurs avantages, avec cependant peu d'inconvénients.
Lors de l'étape de ''dispatch'', l'instruction émise est ajoutée au tampon de réordonnancement, s'il existe. Sans cela, impossible de conserver l'ordre des instructions. Les instructions sont émises dans l'ordre au niveau de l'unité de ''dispatch'', pas au niveau de l'étage de ''scheduling''. Aussi, pour remplir le ROB, cela doit se faire au dernier moment où les instructions sont émises dans l'ordre, soit en sortie de l'unité de ''dispatch''. De plus, les micro-opérations mémoire sont traitées à part des autres, elles sont envoyées directement à l'unité mémoire. La gestion des calculs d'adresse est quelque peu complexe, mais ils peuvent être fait soit dans l'unité mémoire, soit dans les ALU entières, peu importe. Il y a aussi un système de contournement complexe entre ALU/FPU et unité mémoire, qui n'est pas représenté dans le schéma ci-dessous.
[[File:Processeur avec plusieurs fenêtres d'instruction.png|centre|vignette|upright=2.5|Processeur avec plusieurs fenêtres d'instruction.]]
Il faut noter que quelques processeurs utilisent des méthodes intermédiaires entre les trois solutions précédentes. Par exemple, les processeurs AMD Zen utilisent une fenêtre d'instruction décentralisée un peu particulière. Les unités de calcul entières disposent d'une fenêtre d'instruction rien que pour elles, sur ce processeur. Les instructions flottantes sont quant à elles alimentées par une file de micro-opération, pas une fenêtre d'instruction ! La raison à ce choix est un compromis entre performance et cout en circuit. L'exécution dans le désordre est peu efficace sur les instructions flottantes, car les CPU n'ont que peu d'ALU flottantes séparées. Vu le cout en circuits d'une fenêtre d'instruction, les concepteurs du processeur ont préféré utiliser une file de micro-opération pour les opérations flottantes.
===Avantages et inconvénients de chaque implémentation===
Un processeur contient soit une fenêtre d'instruction unique, soit plusieurs fenêtres d'instruction séparées. Typiquement, les fenêtres d’instruction séparées sont spécialisées, dans le sens où on a une fenêtre pour les instructions flottantes, une autre pour les instructions entières, une autre pour les accès mémoire, etc. Pour résumer, on a le choix entre une grosse fenêtre généraliste et plusieurs fenêtres spécialisées, sauf que la fenêtre centralisée est lente et complexe, contrairement aux fenêtres spécialisées. Les deux méthodes ont des inconvénients et des avantages différents.
Dans les grandes lignes, l'avantage des fenêtres décentralisées est qu'elles sont plus petites qu'une grosse fenêtre d'instruction. Cela simplifie la gestion du contournement et de la répartition des instructions sur les unités de calcul, comme on le verra dans quelques paragraphes. Elles sont donc moins complexes et plus rapides. L'autre avantage, c'est qu'il est possible de démarrer l'exécution de plusieurs instructions simultanément : une instruction par fenêtre décentralisée, contre une par fenêtre d'instruction.
Mais les fenêtres décentralisées sont souvent sous-utilisées, partiellement remplies, contrairement aux fenêtres d'instruction. Il arrive qu'une fenêtre d'instruction soit remplie, alors que les autres sont vides. Par exemple, prenons un processeur avec une fenêtre d'instruction reliée à la FPU, et une autre reliée aux autres ALUs. Si le processeur n'exécute que des opérations flottantes, la fenêtre reliée à la FPU sera pleine, alors que l'autre sera vide. L'exécution des instructions dans le désordre est alors limitée par la petite taille de la fenêtre, qui ne peut plus accepter de nouvelle instruction flottante. Avec une fenêtre unique, on n'aurait pas eu ce problème : on aurait eu une énorme fenêtre d'instruction remplie d'instruction flottante, au lieu d'une petite fenêtre spécialisée.
Il est maintenant temps de voir en quoi sont faites les fenêtres d’instruction, qu'elles soient centralisées ou décentralisées. Nous allons aussi voir quelle est la logique d'émission associée.
==L'implémentation d'une fenêtre d'instruction : généralités==
Une fenêtre d'instruction est une mémoire qui mémorise des micro-opérations. Elle est cependant plus complexe qu'une mémoire RAM ou qu'une FIFO et ressemble un petit peu à une mémoire cache. Elle dispose d'un port d'écriture et d'un port de lecture, au minimum.
Le port d'écriture permet à l'unité de décodage d'insérer une micro-opération dedans, d'ajouter la micro-opération qui vient d'être décodée. Si une instruction machine est décodée en plusieurs micro-opération, deux possibilités. La première est de les insérer un par un, ce qui fait qu'on peut se contenter d'un seul port d'écriture. L'autre possibilité est de les ajouter en même temps, ce qui demande plusieurs ports d'écriture.
Le port de lecture sert à émettre une instruction. On part du principe que la fenêtre d'instruction ne peut émettre qu'une seule instruction à la fois, car les processeurs que nous avons vu précédemment sont de ce type. Mais nous verrons dans quelques chapitre qu'il existe des processeurs dits superscalaires, qui sont capables d'émettre plusieurs instructions par cycle. Dans ce cas, la fenêtre d'instruction a un seul port de lecture. Un processeur superscalaire, quant à lui, doit avoir un port de lecture par unité de calcul, ce qui fait beaucoup plus.
===Les entrées d'une fenêtre d’instruction===
Les fenêtres d'instruction sont composées d''''entrées''', des mots mémoire qui stockent une instruction. Une instruction réserve une entrée après son décodage et la libère dès qu'elle est envoyée aux unités de calcul. Une entrée contient l'opcode (les signaux de commande à envoyer à l'ALU), le registre de destination du résultat, les registres de chaque opérande, et éventuellement des signaux de commande en plus. De plus, chaque opérande est couplée à un bit de disponibilité, qui indique si elle est disponible. Quand un opérande est écrit dans les registres, le bit de présence correspondant est mis à jour. Enfin, chaque entrée possède un bit ''empty'' qui indique si elle est vide, cette information étant utile pour réserver des entrées.
[[File:Entrée d'une station de réservation - sans les tags.png|centre|vignette|upright=3|Entrée d'une fenêtre d'instruction]]
Nous verrons dans quelques chapitres que certaines fenêtres d'instruction sont capables de mémoriser les opérandes des instructions. De telles fenêtres d'instruction seront appelées, dans ce cours, des '''stations de réservation'''. Les entrées des stations de réservation sont assez semblables à celle des fenêtres d'instruction, à un détail près : les opérandes de l'instruction sont enregistrées directement dans des champs séparés. Il y a deux champs, un par opérande, pour stocker l'opérande elle-même, lue depuis les registres ou obtenue via le réseau de contournement. Notons que les champs pour les opérandes viennent en plus des champs pour les noms de registres, encore que les deux peuvent être fusionnés si on veut optimiser le tout.
[[File:Entrée d'une station de réservation.png|centre|vignette|upright=3|Entrée d'une station de réservation.]]
Précisons que la terminologie n'est pas très fiable. Les termes "fenêtre d'instruction" et "station de réservation" ne regroupent pas du tout la même chose d'un papier de recherche à l'autre, d'un bouquin à l'autre, d'un chercheur à l'autre, d'un ingénieur à l'autre, d'un professeur à l'autre. Le terme initial vient pourtant de l'article original sur l'algorithme de Tomasulo, où il désignait une fenêtre d'instruction associée à une unité de calcul précise. Mais le terme a ensuite été réutilisé, ce qui fait que certains processeurs avec plusieurs unités de calcul sont décrit comme ayant une unique station de réservation. En bref : c'est le bazar ! Mais nous en reparlerons dans le chapitre sur le renommage de registres, car c'est la place idéale pour les aborder.
Toujours est-il que j'ai fait le choix de confondre les concepts de fenêtre d'instruction et de station de réservation avec les concepts de '''lecture avant émission''' et de '''lecture après émission'''. La différence entre les deux est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation. Dans le cas "avant émission", les opérandes sont mémorisées dans la station de réservation. Et cette différence a une influence sur le pipeline du processeur, le banc de registres et tout ce qui s'en suit. Un désavantage de la lecture avant émission est qu'il faut stocker les opérandes après lecture, ici dans les stations de réservation. Mais un avantage est que le banc de registre a besoin de moins de ports de lecture, car une bonne partie des opérandes sont fournies par le système de contournement. Nous verrons cela dans le détail dans le chapitre sur les processeurs superscalaires.
Il a été proposé de fusionner le tampon de ré-ordonnancement avec la fenêtre d'instruction dans une structure unique. La méthode a notamment été utilisée sur les processeurs d'architecture K6 d'AMD. Le tout donnait une structure appelée le '''DRIS''' (''deferred scheduling register renaming instruction shelf''). La différence avec une fenêtre d’instruction normale est que les entrées sont libérée non pas quand l'instruction est exécutée/émise, mais quand elle termine son exécution et quitte le ROB. La fenêtre d'instruction doit alors incorporer des bits pour savoir si telle entrée a émis sont instruction ou non.
===La logique d'éveil et de sélection===
Dans le cas qui va suivre, on suppose que le processeur ne peut émettre qu'une instruction à la fois. Chaque cycle, une instruction est sélectionnée et est envoyée dans le chemin de données, soit à une ALU, soit à une unité d'accès mémoire. La sélection d'une instruction est effectuée par l'unité d'émission, qui est aussi appelée le '''''scheduler'''''. Elle gère la disponibilité des opérandes et la disponibilité des unités de calcul adéquate. Le choix de l'instruction émise doit être le plus pertinent possible, pour des raisons de performances.
Pour le premier point, l'unité d'émission détermine quelles instructions sont candidates pour l'émission. Les ''instructions candidates'' sont celles dont tous les opérandes sont prêts. Le processeur contient un circuit qui détecte les instructions candidates : la '''logique d’éveil''' (''wake-up logic''). Une fois que la logique d'éveil a fait son travail, une instruction est choisie pour s’exécuter en fonction de la disponibilité des unités de calcul adéquates. Ce rôle est dévolu à un circuit qu'on appelle la '''logique de sélection''', ou ''select logic''.
[[File:Fonctionnement complet de la logique d’émission.jpg|centre|vignette|upright=1.5|Fonctionnement complet de la logique d’émission.]]
La logique de sélection doit gérer le fait que les unités de calcul du processeur ne sont pas toutes identiques. Déjà, un processeur a généralement des unités séparées pour les instructions entières et flottantes, des unités spécialisées dans les accès mémoire, parfois des unités spécialisées pour les branchements. Une instruction doit être attribuée à la bonne unité de calcul, on ne doit pas envoyer une instruction entière dans une unité de calcul flottante. De plus, parmi les unités de calcul entières, toutes ne sont pas identiques. Par exemple, il est fréquent d'avoir plusieurs unités de calcul spécialisées dans les opérations simples (additions, soustractions, opérations logiques) avec une unité pour les multiplications/divisions séparées, parfois une unité spécialisée pour les décalages/rotations, etc. Là encore, allouer la bonne instruction à la bonne ALU est complexe. Plus les unités de calcul sont hétérogènes, plus la logique de sélection est compliquée.
Les logiques d'éveil comme de sélection sont assez complexes. Comme on le verra plus bas, la logique de sélection la plus simple, qui gère une seule unité de calcul, est basée sur un encodeur à priorité couplé à d'autres circuits. Et pour gérer plusieurs ALUs, il faut mettre des encodeurs en cascade. Le résultat est que même en prenant une logique de sélection simple, elle sera assez lente et gourmande en circuits. Aussi, la logique de sélection met souvent un cycle d'horloge entier pour faire son travail, elle a son propre étage de pipeline attribué. La logique d'éveil est dans le même cas, ce qui fait qu'émettre une micro-opération demande deux cycles d'horloge.
Le défaut est que cela rajoute des cycles d'horloges entre la fenêtre d'instruction et l'ALU, ce qui complique la gestion du pipeline. Ces cycles de retard sont à prendre en compte au moment d'émettre les instructions, les ''scheduler'' doit en tenir compte pour des performances optimales. Sans tenir compte de ces cycles de retard, les instructions arrivent en retard de quelques cycles à l'ALU, cycles de retard durant lesquels l'ALU n'a pas fait de calcul et a été sous-utilisée, sans compter que le système de contournement n'est pas utilisé au mieux. Et le problème est encore plus important sur les processeur avec des stations de réservation, qui utilisent la "lecture après émission" : cela rajoute un cycle d'horloge en plus, un temps de retard en plus.
===La logique d'éveil : l'émission anticipée===
La logique d'éveil doit tenir compte du fait qu'il y a plusieurs étages entre l'émission et l'exécution sur une unité de calcul. Par exemple, un processeur a souvent entre un et à cinq étages entre la file d'attente et les unités de calcul. Il faut lire les registres, gérer le contournement, et cela peut prendre quelques cycles d'horloge. Sur le Pentium 4, on trouve 6 étages entre la fenêtre d’instruction et l'entrée de l'ALU. Si les micro-opérations sont émises quand les opérandes sont disponibles, il y aura un délai de quelques cycles avant qu'elles atteignent l'ALU. Alors qu'idéalement, on souhaiterait que la micro-opération atteigne l'ALU dès que ses opérandes sont disponibles, pour profiter des techniques de contournement. Pour cela, la logique d'éveil émet les instructions quelques cycles en avance pour qu'elles arrivent au bon moment. Il s'agit d'une technique d''''émission anticipée''', dont nous avions parlé dans le chapitre sur le contournement.
: Il faut noter que le problème se pose plus avec des fenêtres d'instruction qu'avec des stations de réservation. Les premières ont un étage en plus entre émission et ALU, vu qu'il faut lire des registres après l'émission.
L'émission anticipée demande d'émettre un signal d'éveil en avance de quelques cycles, pile au bon moment. Par exemples, si on a deux étages entre l'ALU et l'unité d'émission, le signal de réveil sera généré deux cycles avant que le résultat soit effectivement calculé. L'implémentation varie, mais deux solutions sont possibles. La première est de générer le signal d'éveil dans l'ALU, ce qui n'a de sens que pour les opérations comme les multiplications ou les divisions, qui prennent plusieurs cycles. Une autre technique génère le signal de réveil dans une structure centralisée, spécialisée dans la génération des signaux d'éveil. L'unité de composée d'un registre à décalage par registre. Imaginons qu'une instruction est émise, que cette instruction mette N cycles à s'exécuter, et qu'elle écrive dans un registre de destination. L'unité sélectionne alors le registre à décalage associé au registre de destination, et met le bit numéro N à 1. Le registre décalé d'un cran à chaque cycle. Quand le bit sortant est un 1, alors le signal de validité pour l'opérande est généré.
L'émission anticipée marche bien si l'instruction a une durée fixe, connue à l'avance. Et autant c'est le cas pour les instructions arithmétiques et logiques, autant ce n'est pas le cas pour les accès mémoire. Les accès mémoire ont une latence variable : quelques cycles pour un succès de cache, plusieurs dizaines de cycles pour un défaut de cache L1, pas loin de la centaine pour un défaut de cache L2, etc. Et cela pose des problèmes pour l'émission anticipée.
L'émission en avance pour les accès mémoire est purement spéculative : le processeur émet des instructions en supposant que la lecture entrainera un succès de cache, quitte à annuler l'instruction en cas de défaut de cache. Mais faire ainsi demande non seulement d'annuler les instructions émises en avance, mais aussi de ne pas retirer les instructions émises en avance de la fenêtre d'instruction. Elles sont émises, mais une copie de sauvegarde est conservée dans la fenêtre d'instruction. Si le processeur détecte un défaut de cache, il peut alors ré-exécuter l'instruction. S'il détecte un succès de cache, les instructions lecture-dépendante sont retirées de la fenêtre d'instruction, la copie de sauvegarde est devenue inutile et est donc jetée.
===La logique de sélection : âge ou position ?===
La logique de sélection est primordiale pour de bonnes performances. Il existe deux méthodes principales pour sélectionner l'instruction à exécuter. La première est la plus simple : elle se base sur lé numéro de l'entrée. En effet, une fenêtre d'instruction est avant tout une sorte de mémoire, un mix entre mémoire associative, mémoire RAM, et FIFO. Et chaque entrée a un numéro, une adresse mémoire. Nous parlerons de numéro dans ce qui suit, car ce sera plus simple, le terme d'adresse mémoire étant peut-être un peu exagéré dans le contexte des fenêtres d'instruction.
Toujours est-il que la fenêtre d'instruction est adressable dans le sens où on peut lui envoyer le numéro de l'entrée voulue, et la fenêtre d’instruction renvoie le contenu de l'entrée sur son port de lecture. Et c'est ce que fait la logique de sélection : elle génère le numéro de l'entrée à lire. Le numéro est alors envoyé sur l'entrée d'adresse de la fenêtre d’instruction, et elle fournit la micro-opération associée sur son port de lecture, son '''port d'émission'''.
La première méthode de sélection, donc. Elle est basée sur sur le numéro de l'entrée. Si plusieurs entrées sont éveillées, la logique de sélection prend celle avec le plus petit numéro. Elle porte le nom de '''sélection par position''', sous-entendu position dans la fenêtre d'instruction. Vous l'avez peut-être deviné, mais la logique de sélection est alors un encodeur à priorité. Le circuit de sélection ne se résume donc pas seulement à un encodeur à priorité, mais c'est le circuit central. L'avantage d'une telle méthode de sélection est qu'elle est simple à implémenter : un encodeur à priorité est un circuit connu, facile à implémenter. Mais on remarque un défaut : un encodeur est un circuit assez rapide, mais qui utilise beaucoup de transistors, beaucoup de portes logiques, surtout si on veut qu'il soit rapide. Ce qui explique que la logique de sélection ait son propre étage de pipeline rien que pour elle.
La seconde méthode s'appelle la '''sélection par âge''', au nom assez transparent. L'idée est de privilégier l'émission de l'instruction la plus ancienne. Elle demande que la fenêtre d'instruction trie les micro-opérations par ordre d'insertion dans la fenêtre d'instruction, ce qui en fait une pseudo-FIFO. Le tri est partiel, car les instructions peuvent sortir de la fenêtre d’instruction à tout moment. Compacter la fenêtre d'instruction, à savoir regrouper toutes les entrées valides est une idée, mais elle est compliquée à implémenter. Aussi, elles préfèrent encoder des informations sur l'ordre d'émission dans chaque entrée. L'encodeur à priorité doit alors travailler non pas sur des nombres entiers liés à l'ordre d'insertion dans la pseudo-FIFO. Elle est encore plus gourmande en circuits que la méthode précédente.
Les processeurs haute performance utilisent souvent des méthodes hybrides. Par exemple, les processeurs AMD de microarchitecture Bulldozer utilisaient une méthode intermédiaire. Ils utilisaient une fenêtre d'instruction couplée à un encodeur à priorité pour la sélection par position, mais complémentaient le tout avec une unité qui mémorisait l'instruction la plus ancienne dans la fenêtre d’instruction. Le mécanisme de sélection par âge était utilisé en priorité. Mais si l'instruction la plus ancienne n'était pas prête, le processeur basculait sur le mécanisme de sélection par position.
===La compaction des fenêtres d'instruction===
À chaque cycle, les instructions décodées sont ajoutées dans la fenêtre d'instruction, dans des entrées vides. Vu que les instructions quittent celle-ci dans le désordre, ces vides sont dispersés dans la fenêtre d'instruction, ce qui pose problème pour déterminer où placer les nouvelles instructions. La solution la plus triviale consiste à conserver une liste des vides, mise à jour à chaque insertion ou émission d'instruction. Une autre solution consiste à éliminer les vides en compactant la fenêtre d'instruction à chaque cycle d'horloge. Des circuits se chargent de détecter les vides et de regrouper les instructions en un unique bloc. Il faut signaler que certaines processeurs arrivent à se passer de cette étape de compactage, mais au prix de fenêtres d'instruction nettement plus complexes.
Autre problème : quand il faut choisir quelle instruction émettre, il y a toujours plusieurs candidats. Si on choisit mal, des instructions restent en attente trop longtemps parce que d'autres instructions plus jeunes leur passent devant. Pour éviter cela, les instructions les plus vielles, les plus anciennes, sont prioritaires. Pour cela, on peut utiliser une FIFO un peu spéciale pour la fenêtre d'instruction. Si les ajouts d'instruction se font dans l'ordre, les instructions ne quittent pas forcément la fenêtre d'instruction dans l'ordre imposé par une FIFO : les instructions restent triées dans leur ordre d'ajout, même s'il y a des vides entre elles. Dans ces condition, il est préférable que le compactage conserve l'ordre FIFO des instructions. Dans ces conditions, l'instruction la plus ancienne est celle qui est située à l'adresse la plus faible : le circuit de sélection peut donc être fabriqué avec des encodeurs, et est relativement simple.
==Les fenêtres d'instruction basées sur une matrice de dépendances==
L'implémentation la plus simple étend le fonctionnement d'un pipeline dynamique usuel. Rappelez-vous le chapitre sur les pipeline dynamiques. Nous avions vu que de tels pipelines émettaient une instruction par cycle, quitte à émettre des bulles de pipeline en cas de dépendance bloquante. Et il est possible d'améliorer leur unité d'émission pour qu'elle soit compatible avec l'exécution dans le désordre.
===Rappels sur le registre de réservation/disponibilité===
L'émission d'une instruction est gouvernée par un ''registre de réservation'' qui indique quels registres sont réservés par une instruction, et ceux qui sont libres. Un registre réservé est un registre qui sera écrit par une instruction en cours d'exécution, mais qui n'a pas encore écrit son résultat. Les instructions candidates doivent avoir leurs opérandes dans des registres libres. Sinon, c'est signe que leurs opérandes sont réservées en écriture, donc pas encore écrites, donc pas disponibles. Si une instruction candidate veut lire ce registre, c'est signe qu'il y a une dépendance RAW : elle veut lire un registre réservé, qui est destiné à être écrit par une instruction en vol. Si elle veut écrire, c'est signe qu'elle veut écrire dans un registre où une autre instruction veut écrire : c'est une dépendance WAW. Dans les deux cas, l'instruction n'est pas émise.
Le registre de réservation encode les registres réservés comme suit : chaque bit est associé à un registre. Le bit numéro 0 est associé au registre numéro 0, le bit numéro 1 au registre numéro 1, etc. Là, En clair, les numéros de registres réservés sont encodés en représentation non pas binaire, mais en représentation ''one-hot'' (vue au premier chapitre).
{|class="wikitable"
|+ Registre de réservation
|-
! Registre 7 !! Registre 6 !! Registre 5 !! Registre 4 !! Registre 3 !! Registre 2 !! Registre 1 !! Registre 0
|-
| 0 || 0 || 1 || 0 || 0 || 1 || 0 || 0
|}
Avant d'émettre une instruction, l'unité d'émission extrait les registres opérande/destination et les convertis dans la même représentation que le registre de réservation, à savoir en représentation ''one-hot''. Le circuit de traduction binaire vers ''one-hot'' est, pour rappel, un simple décodeur. Puis, on fait un OU logique entre les sorties des décodeurs. Le résultat est un masque qui indique quels registres sont lus par l'instruction, encodé en représentation ''one-hot''. Appelons-le le '''masque d'opérandes'''. Le masque est alors comparé au registre de réservation pour vérifier si l'instruction peut être émise.
[[File:Unité d'émission simple, dans l'ordre.png|centre|vignette|upright=2|Unité d'émission simple, dans l'ordre]]
Si l'émission est autorisée, le registres de réservation est là aussi mis à jour. Le registre de réservation est aussi mis à jour dès qu'une instruction enregistre son résultat dans les registres, ou alors dès qu'il est disponible pour le contournement. Le bit associé au registre repasse alors à 0 (ou à 1). Sans contournement, la mise à jour se fait alors à la toute fin de l'instruction, quand elle se termine, lors de la dernière étape d'enregistrement, à la fin de son pipeline. Avec, elle se fait quand l'instruction quitte l'unité de calcul.
===L'extension à l'exécution dans le désordre : l'éveil par matrice de bits===
La différence entre un pipeline dynamique et l'exécution dans le désordre est que l'on passe d'une instruction à émettre à plusieurs. Plusieurs instructions sont mises en attente dans une fenêtre d’instruction, et y attendent leur tour. L'idée est alors d'envoyer le registre de disponibilité à toutes les entrées, à toutes les instructions en attente. Ainsi, on sait quelles sont les instructions qui peuvent s'exécuter et celles qui ne le peuvent pas. L'implémentation est assez simple, elle ressemble beaucoup à l'implémentation d'un pipeline dynamique simple, si ce n'est que des circuits sont dupliqués.
L'implémentation utilise des fenêtres d'instruction légèrement différentes de celles introduites plus haut. Elles n'utilisent notamment pas de bits de disponibilité, mais autre chose. Lorsqu'une instruction est ajoutée à la fenêtre d'instruction, le masque de disponibilité des opérandes est stocké dans la fenêtre d'instruction. A chaque cycle, le registre de disponibilité est envoyée à toutes les entrées, et est comparé avec tous les masques de disponibilité des opérandes. Si une comparaison renvoie un 1, alors l'instruction de l'entrée est une instruction candidate à l'émission.
[[File:Unité d'émission dans le désordre basée sur un registre de disponibilité.png|centre|vignette|upright=2.5|Unité d'émission dans le désordre basée sur un registre de disponibilité]]
L'ensemble est implémenté avec l'aide d'une '''matrice de bits''', où chaque ligne correspond à une entrée et chaque colonne à un registre. À chaque croisement entre une ligne et une colonne, on trouve un bit. Si le bit de la ligne n et de la colonne m est à 1, cela veut dire : l'instruction dans l'entrée n a besoin de lire le registre m. S’il est à 0, alors cela veut dire que l'instruction stockée dans la ligne n n'a pas besoin de la donnée dans le registre m.
[[File:Planificateur à matrice de bits.jpg|centre|vignette|upright=2|Planificateur à matrice de bits.]]
Lorsqu'une instruction réserve une entrée, elle initialise la ligne en fonction des registres qu'elle souhaite lire. Pour vérifier si une instruction a ses opérandes prêts, le processeur compare le registre de disponibilité à la ligne associée à l'instruction/entrée. A chaque intersection ligne-colonne, se trouve un comparateur de 1 bit, qui détecte si le registre est demandé et disponible. Les résultats de cette porte sont ensuite envoyés à une porte ET, qui fait le gros du travail.
[[File:Logique de détection de la disponibilité d'une instruction.jpg|centre|vignette|upright=2|Logique de détection de la disponibilité d'une instruction.]]
==Les fenêtres d'instruction basées sur une mémoire associative==
Les processeurs modernes utilisent une mémoire associative pour la fenêtre d'instruction. Les mémoires associatives ont été abordées il y a quelques chapitres, mais faisons quelques rappels. Les mémoires associatives servent à accélérer la recherche de données dans un ensemble. Or, une fenêtre d'instruction vérifie régulièrement si chaque entrée est prête. Il s'agit d'un processus de recherche d'une instruction qui respecte une condition bien précise dans la mémoire, ce qui fait que les mémoires associatives sont donc tout indiquées.
Comme vous le savez, les signaux de commande d'une instruction sont propagés avec celle-ci dans le pipeline, le nom du registre de destination ne faisant pas exception. Le nom de registre est envoyé à la fenêtre d'instruction lors de l'écriture du résultat dans les registres. S'il y a correspondance, l'opérande est disponible et son bit de disponibilité est mis à 1. On peut adapter cette méthode pour tenir compte du contournement assez simplement.
[[File:Détection des dépendances par propagation du registre de destination.png|centre|vignette|upright=2|Détection des dépendances par propagation du registre de destination.]]
En conséquence, le circuit de détection des dépendances est constitué d'un grand nombre de comparateurs : un par champ « nom de registre » dans chaque entrée. La logique d'éveil/''wake up'' regroupe tous les comparateurs, la logique de sélection est un circuit situé en-dehors de la mémoire associative. Les entrées sont dans la mémoire associative elle-même.
[[File:Gestion des bits de validité.png|centre|vignette|upright=2|Gestion des bits de validité.]]
L'implémentation la plus simple est celle du schéma précédent, où on utilise une mémoire associative unique. Une version plus élaborée sépare la fenêtre d'instruction en deux parties : une mémoire qui mémorise les registres des opérandes, une autre mémoire pour le reste. La mémoire pour les registres opérandes est la mémoire associative proprement dite, c'est elle qui est associées aux comparateurs, à la logique de ''wake-up''/réveil, etc. Par contre, l'autre mémoire est une mémoire RAM simplifiée, qui mémorise les informations qui n'ont pas besoin des comparateurs. L'opcode de l'instruction est là-dedans, par exemple.
===Les optimisations de la consommation d'énergie : la désactivation des portions inutilisées===
Le problème avec des fenêtres d'instruction de ce type est leur forte consommation énergétique, ainsi que le grand nombre de portes logiques utilisées, qui deviennent prohibitif pour des fenêtres d'instruction un peu grosses. Pour résoudre ce problème, certains ont optimisé les comparateurs. D'autres ont tenté de profiter du fait que la majorité des bits dans les entrées sont composés de zéros. Bref, les optimisations purement matérielles sont légion.
Une première solution est de désactiver les entrées qui sont inutilisées, vides, sans instruction. Pour cela, on fait précéder les comparateurs par un circuit qui les connecte ou déconnecte des lignes de bit. Le circuit est commandé par le bit ''empty'' qui indique si une entrée est vide ou non. L'avantage est que la consommation d'énergie de la fenêtre d'instruction devient alors proportionnelle au nombre d'entrées non-vides, et non au nombre d'entrées total. Du moins, en apparence, les entrées non-utilisées doivent quand même être alimentées pour fonctionner, mais l'activité de comparaison disparait dans les entrées vides. Il est possible d'étendre cette technique aux entrées dont les opérandes sont prêtes. Les gains sont généralement assez bons, avec une réduction de consommation variant entre 30 et 50%, avec une implémentation très simple et très économe en circuits.
Une autre solution, assez simple à mettre en place, consiste à désactiver une partie de la fenêtre d'instruction sin elle est inutilisée. Pour cela, il faut segmenter la fenêtre d'instruction avec la technique du ''wire partitionning''. Pour rappel, une fenêtre d'instruction est une mémoire associative. Et comme toute mémoire associative, elle contient des fils, les lignes de bit, qui relient les entrées/sorties à toutes les cellules mémoire. Plus une ligne de bit est longue, plus elle consomme d'énergie et plus le temps de lecture/écriture est long.
L'idée est de segmenter les lignes de bits en plaçant des répéteurs qui recopient la tension d'un segment au suivant. Les répéteurs sont des tampons trois-états, ce qui permet de déconnecter les portions inutilisées de la ligne de bit. Ce faisant, la fenêtre d'instruction est découpée en segments, qui peuvent être désactivés si besoin. Si il y a peu d'instructions chargées dans la fenêtre d'instruction, les portions inutilisées sont désactivées. La technique marche d'autan mieux si les instructions sont compactées au début de la fenêtre d'instruction.
[[File:Wire partitionning.png|centre|vignette|upright=2|Wire partitionning]]
Tout le problème est de savoir quelles portions désactiver. La technique est assez simple si la fenêtre d'instruction est compactée, à savoir si toutes les instructions sont régulièrement regroupées au début de la mémoire CAM. Mais même avec cette optimisation, la décision de désactiver une portion de la fenêtre d'instruction n'est pas à prendre à la légère. Il ne faut pas désactiver une portion contenant des instructions pouvant être émise sous peu. La décision dépend de paramètres variés : proportion d'entrée vides, quantité d'entrées récemment activées/désactivées récemment, présences d'entrées avec un ''match'' d'opérandes récent, etc.
Il est important de préciser que les techniques en question fonctionnent aussi sur les autres types de fenêtre d'instruction, qui ne sont pas basées sur des mémoires associatives. Nous allons voir celles-ci dans la suite du chapitre, mais gardez à l'esprit que ces optimisations, à savoir désactiver les entrées vides/prêts et partitionner la fenêtre d'instruction, sont des optimisations générales qui marchent avec presque tout.
===L'ajout d'une FIFO pour les instructions déjà prêtes à l'émission===
D'autres techniques permettent de réduire aussi bien le cout en circuits que la consommation d'énergie de la fenêtre d'instruction. L'idée est d'utiliser une mémoire associative quand elle est réellement nécessaire, mais d'utiliser une fenêtre d'instruction "normale" dès que possible.
La méthode la plus simple tient compte du fait que certaines instructions ont déjà toutes leurs opérandes de disponibles à l'émission, mais doivent quand même être mises en attente, parce que l'ALU adéquate est occupée, parce que des dépendances WAW/WAR sont encore là, ou pour tout autre raison. Dans ce cas, elles sont mises en attente non pas dans la fenêtre d'instruction, mais dans une mémoire FIFO toute simple. L'idée est qu'au lieu d'avoir une énorme mémoire associative pour la fenêtre d'instruction, on déplace quelques entrées dans une petite FIFO annexe. Le résultat est un gain en terme d'énergie et de circuits, au prix d'une perte de performance mineure. La perte de performance en question se manifeste si aucune instruction n'a ses opérandes de prêtes à l'émission : le programme n'utilise pas la FIFO et voit alors une fenêtre d'instruction plus petite.
Une amélioration de cette technique tient compte du fait que certaines instructions sont dans un cas intermédiaire. Elles ont déjà une opérande de prête lors de l'émission, mais pas l'autre. Le nombre d'opérandes varie suivant l’instruction : certaines n'ont besoin que d'un seul opérande. De plus, il arrive qu'une opération tout juste décodée ait déjà un ou plusieurs de ses opérandes de prêts. Cette constatation permet d'éviter d'utiliser des comparateurs pour les opérandes déjà prêts. Par exemple, on peut parfaitement utiliser trois fenêtres d'instruction :
* une pour les instructions dont tous les opérandes sont prêts, mais qui sont quand même mises en attente, sans comparateur ;
* une pour les instructions dont un seul opérande manque, qui n'utilise qu'un comparateur par entrée ;
* une pour les instructions dont deux opérandes manquent à l'appel, qui utilise deux comparateurs par entrée.
C'est beaucoup plus économique que d'utiliser une seule grosse fenêtre d'instruction qui contiendrait autant d'entrées que les trois précédentes réunies, avec deux comparateurs par entrée. Certains sont même allés plus loin, et ont proposé de supprimer la fenêtre d'instruction avec deux opérandes par entrée. Les instructions dont deux opérandes sont inconnus au décodage sont stockées dans la fenêtre d'instruction pour instructions avec un comparateur par entrée. Un circuit de prédiction se charge alors de prédire l'opérande manquant, cette prédiction étant vérifiée plus loin dans le pipeline. Il serait cependant étonnant qu'une telle proposition ait donné lieu à la moindre implémentation réelle.
===Les optimisations de la consommation d'énergie avancées===
D'autres chercheurs ont conservé une fenêtre d'instruction unique, avec autant de comparateurs par entrée qu'il y a d'opérandes possibles par instruction. Simplement, le processus de détection des opérandes prêts est légèrement ralenti : on ne vérifie qu'un opérande par cycle. Pour vérifier les deux opérandes d'une entrée, on doit attendre deux cycles.
Enfin, certains chercheurs ont proposé des fenêtres d'instruction segmentées. Les instructions circulent à chaque cycle d'un segment vers le suivant : en conséquence, certains segments conservent les instructions les plus anciennes, un autre les instructions les plus jeunes, etc. Seul le denier segment, celui qui contient les instructions les plus vielles, peut émettre une instruction : la détection des opérandes se fait seulement dans le dernier segment, qui contient les instructions les plus anciennes. On économise ainsi beaucoup de comparateurs.
Certains chercheurs ont tenté de pipeliner l'étape de sélection des opérandes, ainsi que l'étage d'arbitrage. Mais en faisant cela, il faut plusieurs cycles pour détecter qu'une instruction a ses opérandes prêts, ce qui pose problème face à de nombreuses dépendances RAW. Pour éviter de trop perdre en performances, certains chercheurs ont décidé d'utiliser des techniques pour prédire quelles seront les instructions dont les opérandes seront bientôt prêts. Si détecter qu'une instruction est prête prend n cycles, le processeur devra tenter de prédire la future disponibilité des opérandes n cycles en avance pour obtenir des performances optimales.
===Remplacer la mémoire associative par une RAM===
Une autre optimisation possible est de remplacer la fenêtre d'instruction par une mémoire RAM, dont chaque mot mémoire correspondrait à une entrée. Une telle optimisation permet en théorie de se passer des comparateurs associés à chaque entrée, mais au prix de l'ajout de circuits annexes potentiellement couteux.
La première de ces techniques, la '''recherche directe par étiquette''' (direct tag search) fut créée par Weiss et son collègue Smith. Ils cherchaient à améliorer l'algorithme de Tomasulo, un algorithme qu'on expliquera dans quelques chapitres. Rappelons que chaque instruction produit un résultat, qui devra être rapatrié dans une ou plusieurs entrées de la fenêtre d'instruction. L'optimisation de la recherche directe par étiquette fonctionne dans le cas où le résultat de l'instruction n'est utilisé que par une seule entrée, et pas plusieurs.
Le principe est simple : chaque champ « opérande » d'une entrée est adressable via le nom de registre de cette opérande. Pour faire le lien entre entrée et nom de registre, la recherche directe par étiquette ajoute une table de correspondances matérielle, une mémoire RAM qui mémorise les adresses des champs « opérande » des entrées. Les adresses envoyées dans cette mémoire sont les noms de registres des résultats. Lors de l'émission, la table de correspondances est mise à jour. Les numéros des champs « opérande » réservés lors de l'émission sont mémorisés dans les mots mémoire qui correspondent aux deux registres sources. Toutefois, une petite vérification est faite lors de l'émission : si il y a déjà une correspondance dans la table pour un registre source, alors une autre instruction compte lire ce registre. Pour éviter d'écraser les données de l'instruction précédente dans la table de correspondances, l'émission de l'instruction est bloquée.
[[File:Recherche directe par étiquette.png|centre|vignette|upright=2|Recherche directe par étiquette.]]
==Les fenêtres d’instruction avec préplanification==
Avec la '''préplanification''' (''prescheduling''), la fenêtre d’instruction est composée de mémoires FIFO dans lesquelles les instructions sont triées dans l'ordre d'émission. Il n'y a pas de logique d’émission proprement dite, celle-ci se bornant à vérifier si l'instruction située au début de la FIFO peut s’exécuter. Par contre, le préplanificateur détecte les dépendances et en déduit où insérer les instructions dans le tampon d'émission, à l'endroit le plus adéquat.
[[File:Préplanification.jpg|centre|vignette|upright=2|Préplanification.]]
===L'usage de FIFO multiples===
La technique de préplanification la plus simple utilise plusieurs FIFO. Quand une instruction a une dépendance avec une autre instruction, les deux sont placées dans la même FIFO. Pour cela, le préplanificateur détecte les chaines d'instructions dépendantes, et place les instructions dans la FIFO adéquate.
A chaque cycle, l'unité d'émission lit une micro-opération dans chaque FIFO et vérifie si elle peut l'émettre. Si la micro-opération n'a pas ses opérandes disponibles, ou qu'une dépendance structurelle survient, alors la FIFO est bloquée. La micro-opération attend que la dépendance soit résolue, bloquant toutes les instructions précédentes. Mais les autres FIFO ne sont pas bloquées, laissant de la marge de manœuvre.
[[File:Préplanification par FIFO multiples.png|centre|vignette|upright=1.5|Préplanification par FIFO multiples.]]
===La technique du tampon trié===
La technique du '''tampon trié''' trie les instructions selon le temps d'attente avant leur exécution. Les instructions qui sont censées s’exécuter bientôt sont insérées au tout début de la FIFO, tandis que les instructions qui s’exécuteront dans un long moment sont insérées vers la fin. Là encore, l'unité d'émission lit une micro-opération dans la FIFO, et détermine si elle peut être émise. Si une dépendance survient, l'émission est retardée, et la FIFO est bloquée.
[[File:Préplanification par tampon trié.png|centre|vignette|upright=1.5|Préplanification par tampon trié.]]
La technique a cependant quelques problèmes assez importants. Le premier problème est que la FIFO est bloquée lorsqu'une micro-opération ne peut être émise. Le second problème est que le temps avant exécution n'est pas toujours connu, notamment pour les instructions d'accès mémoire. Et cela se répercute sur les instructions dépendantes de celles-ci. Pour résoudre ce genre de problèmes, l'usage d'un pipeline à ''replay'' est possible, et est même l'une des meilleures solution possible, malgré ses défauts.
L'idée est que les lecture sont pré-planifiées en supposant qu'elles font un succès de cache L1. Si la prédiction est fausse, la lecture est ré-exécutée plusieurs cycles plus tard, en supposant qu'elle fait un succès dans le cache L2, et ainsi de suite. Elle est alors ré-insérée dans la FIFO, elle est pré-planifiée une seconde fois. Il en est de même si une micro-opération ne peut pas être émise : il suffit de la réinsérer dans la FIFO au bon endroit, en attendant que ses dépendances soient résolus. Ainsi, l'instruction fautive ne bloque pas la fenêtre d’instruction, et retente sa chance autant de fois qu'il le faut.
[[File:Préplanification par fenêtre d’instruction scorebarodée.png|centre|vignette|upright=1.5|Préplanification par fenêtre d’instruction scorebarodée.]]
Une autre solution utilise deux fenêtres d’instruction : une FIFO gérée par la préplanification, et une vraie fenêtre d'instruction pour les instructions dont le temps d'attente ne peut pas être déterminé. Il est possible de mettre cette dernière avant la préplanification, afin de gérer les temps d'attente des instructions d'accès mémoire. Quand le temps d'attente d'une lecture devient connu, ses instructions dépendantes sont gérées avec préplanification.
[[File:Préplanification avec fenêtre d’instruction.png|centre|vignette|upright=2.5|Préplanification avec fenêtre d’instruction.]]
===Les fenêtres d'instruction avec allocation temporelle statique===
L''''allocation temporelle statique''' correspond à une technique utilisée sur les processeurs Cuzco de l'entreprise Condor. La technique en question a été présenté en 2025, mais des idées similaires ont été présentées dans la littérature académique. Elle ressemble beaucoup aux techniques de pré-planification, avec cependant quelques différences.
L'idée est simple : le processeur dispose de plusieurs fenêtres d'instruction, qui mettent en attente les micro-opérations. Lors de l'étape de renommage de registres, le processeur détermine dans combien de cycles d'horloge la micro-opération sera prête pour exécution. La micro-opération est alors mise en attente durant ce nombre de cycles, dans les fenêtres d'instruction, puis est émise une fois ce nombre de cycles écoulés. Ce faisant, les fenêtres d'instruction n'ont pas besoin de détecter la disponibilité des opérandes à chaque cycle, pour chaque micro-opération en attente. Le ''timing'' de l'émission des micro-opérations est décidé à l'avance.
L'implémentation exacte n'est pas connue, mais plusieurs solutions sont imaginables. Par exemple, la fenêtre d'instruction peut simplement être composée de plusieurs mémoires de petite taille, chacune contenant les micro-opérations destinées à être exécutées dans N cycles, N étant différent pour chaque mémoire. Une autre solution, plus réaliste, utilise une fenêtre d'instruction dans laquelle la micro-opération est couplée à des champs pour les opérandes, et un champ compteur. Le champ compteur est initialisé avec le nombre de cycles à attendre, et est décrémenté à chaque cycle d'horloge. Quand il atteint zéro, la micro-opération est émise.
Vous remarquerez que la méthode ne marche que si toutes les micro-opérations prennent un nombre fixe de cycles d'horloge. Et autant c'est le cas pour les micro-opérations arithmétiques ou logiques, autant les accès mémoire ne rentrent pas dans ce cadre. En théorie, on ne sait pas combien de temps prendra un accès mémoire. Et cette incertitude se répercute sur les instructions dépendantes d'un accès mémoire.
Pour éviter cela, le processeur réutilise un pipeline à ''replay'' tel que vu dans le chapitre précédent. L'unité de renommage de registre suppose que tout accès mémoire fait un succès de cache L1, et décide des ''timings'' d'émission sous cette hypothèse. Si une lecture subit un défaut de cache, elle est ré-exécutée, de même que toutes les instructions dépendantes. Pour cela, le registre de destination de la lecture est marqué comme invalide, grâce à un bit spécifique attaché au registre. Toute instruction qui lit une opérande invalide est elle aussi ré-executée de zéro.
Pour simplifier, l'unité d'émission ressemble à une unité d'émission dans l'ordre, avec un registre de disponibilité, qu'on aurait amélioré pour aller au-delà de la simple détection des dépendances d'instruction. Pour déterminer les ''timings'' d'émission, à savoir quand émettre une micro-opération, le processeur utilise une unité d'émission à deux étages. Le premier étage est appelé le ''register scoreboard'', le second étage est appelé la ''Time Ressource Matrix''. Les deux étages communiquent entre eux, comme on va le voir, et leurs noms donnent une idée de ce qu'ils font. Le premier étage gère les dépendances de registres, le second gère les dépendances structurelles.
Le premier étage est un registre de disponibilité amélioré, qui gère les dépendances de registre. Cet étage sait quand tel registre sera écrit, et donc que l'opérande écrite dedans sera disponible à partir de tel cycle d'horloge. Il le sait car l'étage suivant l'aura prévenu, mais laissons cela de côté pour le moment. Quand une micro-opération rentre dans cet étage, il vérifie les dépendances avec les registres et les micro-opérations antérieures. Vu qu'il sait quand les registres seront disponibles, il peut déterminer quand l'instruction pourra s'exécuter. Par exemple, si une micro-opération lit les deux registres R7 et R15, qui sont disponibles respectivement dans 5 et 7 cycles, le premier étage sait que la micro-opération devra attendre 7 cycles.
Mais tout cela ne suffit pas à déterminer quand émettre une micro-opération. En effet, il fait aussi gérer les dépendances structurelles, ce qui est le boulot du second étage. Le second étage utilise pour cela une ''Time Ressource Matrix'', qui mémorise l'occupation de diverses "ressources", pour les 256 cycles d'horloge à venir. Les ressources en question sont les ports de lecture/écriture du banc de registre, les ALUs utilisées, si la micro-opération utilise l'unité mémoire, etc. Le second étage prend en entrée une micro-opération, détermine quelles "ressources" elle utilise, puis regarde leur disponibilité. Elle détecte alors les dépendances structurelles et détermine alors quand peut s'exécuter la micro-opération. Elle peut retarder l'émission de quelques cycles, si une dépendance structurelle est détectée.
Une fois sortie du second étage, on sait quand la micro-opération va être émise, à quel cycle précisément. Et vu que la durée de l'instruction est fixe, on sait quand cette micro-opération va se terminer, quand son résultat sera disponible. Cela permet de mettre à jour la ''Time Ressource Matrix'', pour préciser que le port d'écriture sera occupé à tel moment. De plus, cette information est transmise au premier étage, qui sait alors quand est enregistré le résultat dans le registre de destination. C'est comme cela que le premier étage sait quand un registre est disponible : le second étage le prévient, quand il émet une instruction !
La ''Time Ressource Matrix''mémorise l'occupation des "ressources" pour les 256 cycles d'horloge à venir. Cependant, elle ne vérifie pas les dépendances pour les 256 cycles suivants. Quand elle reçoit une micro-opération, elle teste les dépendances structurelles pour seulement les 8 prochains cycles maximum. Si la micro-opération ne peut pas être émise pendant ces 8 cycles, le pipeline est bloqué, un ''pipeline stall'' est émis. Pour être plus précis, vu que le processeur peut décoder 8 micro-opérations en même temps, cela permet de ne consulter que 64 cycles d'horloge sur les 256.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les premiers processeurs Intel
| prevText=Les premiers processeurs Intel
| next=Le renommage de registres
| nextText=Le renommage de registres
}}
</noinclude>
0g7ueq74d5kra7yhw9rovesgolccsgf
Fonctionnement d'un ordinateur/La prédiction de branchement
0
65955
773336
747545
2026-09-27T19:13:40Z
Mewtow
31375
/* La prédiction avec plusieurs unités de branchement distinctes */
773336
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Pour cela, ils utilisent un tampon de destination de branchement amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations qui lui permettent de déduire plus ou moins efficacement quelle adresse de destination est la bonne. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
9d7l8kbqh9gnsmchig4o06o99qi3ssy
773337
773336
2026-09-27T19:32:20Z
Mewtow
31375
/* La prédiction de l'adresse de destination pour les branchements indirects et implicites */
773337
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas.
De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Pour cela, ils utilisent un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise souvent un BTB séparé, distinct du BTB pour les branchements directs. Il est possible d'utiliser un BTB unique, pour tous types de branchements, mais ce n'est pas l'optimal. La différence entre les deux solutions est la suivante. Avec un BTB unique, il faut mémoriser plusieurs adresses de destination par branchements, dans des cases mémoires séparées. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
l88aipkw0p4tw18kl0cm7gzxbxlmggi
773338
773337
2026-09-27T19:33:29Z
Mewtow
31375
/* La prédiction de l'adresse de destination pour les branchements indirects et implicites */
773338
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, mais ce n'est pas l'optimal. Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs.
La différence entre les deux solutions est la suivante. Avec un BTB unique, il faut mémoriser plusieurs adresses de destination par branchements, dans des cases mémoires séparées. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
s7sx8az3pjknlph88ebnd1iwsbym5kn
773339
773338
2026-09-27T19:44:23Z
Mewtow
31375
/* La prédiction de l'adresse de destination pour les branchements indirects et implicites */
773339
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 11101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 11101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
n60nvpp566jscuedd75j68atemeps8f
773340
773339
2026-09-27T19:45:25Z
Mewtow
31375
/* La prédiction de l'adresse de destination pour les branchements indirects et implicites */
773340
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 11101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 11101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
62g9z9m44qvorfemcr0pawuq04k1o1v
773341
773340
2026-09-27T19:45:52Z
Mewtow
31375
/* La prédiction de l'adresse de destination pour les branchements indirects et implicites */
773341
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 1101101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
95vkotkg5ye5nqoshfzs0g2iqb46kxg
773342
773341
2026-09-27T19:46:01Z
Mewtow
31375
/* La prédiction de l'adresse de destination pour les branchements indirects et implicites */
773342
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
6erk4h7qsp4lf2b5e6nxpis94xtf9ly
773381
773342
2026-09-27T22:41:38Z
Mewtow
31375
/* La prédiction avec plusieurs unités de branchement distinctes */
773381
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
===La prédiction de branchement découplée===
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
[[File:Double BTB sur les CPU modernes.png|centre|vignette|upright=2|Double BTB sur les CPU modernes.]]
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente, au moins deux. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
8omep8wwohldfwflztxisevjztqyryx
773382
773381
2026-09-27T22:42:01Z
Mewtow
31375
/* La prédiction avec plusieurs unités de branchement distinctes */
773382
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
==La prédiction de branchement à deux niveaux==
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
[[File:Double BTB sur les CPU modernes.png|centre|vignette|upright=2|Double BTB sur les CPU modernes.]]
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente, au moins deux. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
8w7hg4ldimypi1tal3t9p1bi433a5b2
773384
773382
2026-09-27T22:52:12Z
Mewtow
31375
/* La prédiction de branchement à deux niveaux */
773384
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
l44ozwbfq4eb0hy16e3yhcgvsd64285
773385
773384
2026-09-27T22:52:20Z
Mewtow
31375
/* La prédiction avec plusieurs unités de branchement distinctes */
773385
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction de branchement à deux niveaux==
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
[[File:Double BTB sur les CPU modernes.png|centre|vignette|upright=2|Double BTB sur les CPU modernes.]]
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente, au moins deux. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0, L1 et L2'''. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
ddedxv08lfe4mvsm2fc6c0t3371djob
773400
773385
2026-09-27T23:57:57Z
Mewtow
31375
/* La prédiction de branchement à deux niveaux */
773400
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction de branchement à deux niveaux==
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
[[File:Double BTB sur les CPU modernes.png|centre|vignette|upright=2|Double BTB sur les CPU modernes.]]
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente, au moins deux. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0 et L1'''. Par exemple, les processeurs Bulldozer d'AMD disposent de deux BTBs : une BTB L1 de 512 entrées, et une BTB L2 de 5120 entrées.
Quelques processeurs ont trois niveaux de BTB, avec un niveau L0, L1 et L2. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
inqycreifanrf67yjs88bjw6mthnfw3
773401
773400
2026-09-27T23:58:55Z
Mewtow
31375
/* La prédiction de branchement à deux niveaux */
773401
wikitext
text/x-wiki
Les branchements sont pris en charge par plusieurs étages du pipeline. Exécuter un branchement demande de savoir deux choses : s'il est pris ou non, quelle est l'adresse de destination.
L'adresse de destination est localisée grâce au mode d'adressage du branchement. Avec le mode d'adressage direct, elle est extraite de l'instruction par le séquenceur. Avec l'adressage indirect, elle est lue dans un registre. Avec les branchements relatifs, elle est calculée via un additionneur, qui additionne un décalage au ''program counter''. L'additionneur peut être soit dans l'étage de ''PC Update'', soit dans l'unité de calcul de l'étage d'exécution.
Passons maintenant au résultat du branchement, à savoir s'il est pris ou non. Pour savoir si le branchement est pris, il faut distinguer les branchements conditionnels et inconditionnels. Pour rappel, les branchements inconditionnels regroupent les appels de fonction, les simples instructions de saut, et quelques autres. Ils sont toujours pris, ils s'exécutent toujours. A partir du moment où le décodeur a décodé un branchement inconditionnel, il sait que le branchement est pris. Pour les branchements conditionnels, ils sont pris seulement si une condition précise est respectée. On sait s'ils sont exécutés une fois que la condition est calculée par l'unité de calcul, ou par une unité de branchement spécialisée.
[[File:Gestion des branchements sur un pipeline.png|centre|vignette|upright=2.0|Gestion des branchements conditionnels sur un pipeline.]]
Sur le schéma précédent, les branchements inconditionnels ne sont pas montrés. Pour cela, il faudrait rajouter une connexion du décodeur vers le multiplexeur de branchement. De plus, il faudrait aussi rajouter de quoi retarder l'exécution du branchement seulement une fois que l'adresse est connue. Par exemple, elle est connue un cycle d'horloge plus si l'adresse est lue depuis les registres, comparé à un branchement direct.
==Les branchements et les exceptions imprécises/précises==
Un branchement est censé altérer le ''program counter'' immédiatement, en un cycle d'horloge. Mais sur un pipeline, Le branchement fait bien reprendre le processeur au bon endroit, mais avec du retard. La raison est qu'un branchement met plusieurs cycles pour s'exécuter. Il faut plusieurs cycles pour déterminer quelle est l'adresse de destination, temps qui dépend de là où est lue l'adresse. Et on sait si le branchement est pris seulement au niveau du décodeur, voire au niveau de l'unité de calcul. Le branchement doit traverser plusieurs étages avant qu'on connaisse son résultat. Il y a bien 3 à 4 cycles avant cela. Et pendant ce temps de trajet , des instructions seront chargées dans le pipeline, potentiellement à tord. Si le branchement n'est pas pris, ce n'est pas grave : les instructions n'ont pas été chargées à tord. Mais dans le cas contraire, les instructions n'auraient pas du rentrer dans le pipeline.
Le problème est similaire à celui observé avec les exceptions précises/imprécises. La différence est que les branchements sont des instructions machines, pas les exceptions matérielles. Mais le problème des instructions chargées à tord est le même dans les grandes lignes. Une différence est que les branchements sont résolus plus ou moins au même étage, alors que les exceptions peuvent survenir dans tout le pipeline. Une autre différence, bien plus importante, est que les branchements non-pris ne chargent pas d'instructions à tord et qu'il faut faire la différence entre branchement pris et non-pris.
Dans la suite, nous parlerons d'instructions fautives pour parler des instructions chargées à tord.
Pour corriger ce problème, il existe une solution logicielle. Le compilateur fait suivre les branchements par des instructions qui ne font rien, des NOP (No OPeration) : c'est ce qu'on appelle un '''délai de branchement'''. L'idée est que toutes les instructions chargées à tord sont des NOPs, qui ne font rien. Mais le nombre de NOP à ajouter dépend du pipeline du processeur, ce qui peut poser des problèmes de compatibilité. Mais d'autres solutions, matérielles cette fois, sont utilisées de nos jours.
===L'annulation des instructions au décodage===
Les branchements inconditionnels sont dans un cas plus favorable : ils sont identifiés dans l'unité de décodage et sont toujours pris. D'autres branchements sont dans ce cas : les appels de fonctions, les interruptions logicielles. Avec eux, le décodeur annule les instructions fautives, il les remplace par des instructions NOP. L'étage de décodage sait que les instructions suivant un branchement inconditionnel ont été chargées à tord. Précisément N instructions, avec N le nombre d'étages entre le chargement et le décodage (inclus). Les N instructions suivantes sont tout simplement annulés, elles sont remplacées par des NOPs. L'implémentation demande juste un simple compteur et quelques circuits annexes.
Cependant, la technique ne marche pas du tout pour les branchements conditionnels. En effet, on ne sait pas s'ils sont pris ou non à l'étage de décodage. Du moins pas avec un pipeline normal. Mais certains processeur, basés sur le pipeline RISC classique, calculaient les branchements pendant l'étape de décodage, dans une ALU spécialisée qui travaille en parallèle de l'unité de décodage. Le défaut de cette technique est que l'ALU en question doit lire les registres. Pour rappel, sur ce pipeline, l'étape de décodage effectue la lecture des opérandes dans les registres, en parallèle de l'unité de décodage. Mais ce qu'on n'a pas dit dans le chapitre précédent, c'est qu'elle effectue aussi le calcul des branchements dans une mini-ALU séparée ! Ce faisant, le résultat du branchement est connu dès la fin de l'étape de décodage.
===L'usage du système d'exceptions précises pour les branchements conditionnels===
La solution gère le problème en empêchant les instructions chargées à tord d'enregistrer leurs résultat, afin qu'elles n'aient pas le moindre effet sur l'état du processeur. Le hardware pour les exceptions précises, décrit quelques paragraphes avant, est réutilisé pour cela. Les branchements conditionnels peuvent être pris ou non-pris et on doit tenir compte de ces deux cas. Si le branchement n'est pas pris, les instructions n'ont pas été chargées à tord, elles peuvent enregistrer leur résultat dans les registres. Par contre, ce n'est pas le cas pour les branchements pris, les mécanismes d'exceptions précise doivent alors d'activer. Pour cela, la meilleure solution est que l'unité de calcul génère un indicateur d'exception si le branchement n'est pas pris. L'ALU effectue la comparaison et génère ou non l'indicateur d'exception adéquat suivant le résultat de celle-ci.
L'idée est d'utiliser un indicateur d'exception particulier, qui précise qu'un branchement a été pris, signe que des instructions ont été chargées à tord. L'indicateur est propagé dans le pipeline, jusqu'à atteindre l'étage d'enregistrement dans les registres. Cet étage décide s'il faut autoriser ou non les écritures qu'il recoit, pour annuler les instructions chargées à tord. Le ''program counter'' est corrigé immédiatement, simplement en exécutant le branchement. Reste à savoir qui génère cet indicateur d'exception de branchement pris.
Diverses optimisations visent à éliminer les instructions chargées à tord, à faire en sorte que des instructions utiles soient chargées. Le chapitre suivant va détailler ces optimisations dans le détail, qui sont regroupées sous le terme de prédiction de branchement. Elles ne fonctionnent pas pour les exceptions précises, seulement les branchements.
===La prédiction de branchement===
Les techniques précédentes ont pour défaut que de nombreux cycles d'horloge sont gâchés à cause des instructions fautives. Pour éviter ce gâchis, les concepteurs de processeurs ont inventé l'''exécution spéculative de branchement'', qui consiste à deviner l'adresse de destination du branchement et charger les bonnes instructions dès le départ. Le but est de ne pas charger d'instruction à tord, en prédisant quelle seront les bonnes instructions à charger. Et cela demande de résoudre trois problèmes :
* reconnaître les branchements ;
* savoir si un branchement sera exécuté ou non : c'est la '''prédiction de branchement''' ;
* dans le cas où un branchement serait exécuté, il faut aussi savoir quelle est l'adresse de destination : c'est la '''prédiction de l'adresse de destination''' d'un branchement, aussi appelée ''branch target prediction''.
Pour résoudre le second problème, le processeur contient un circuit qui déterminer si le branchement est pris (on doit brancher vers l'adresse de destination) ou non pris (on poursuit l’exécution du programme immédiatement après le branchement) : c'est l''''unité de prédiction de branchement'''. La prédiction de direction de branchement est elle aussi déléguée à un circuit spécialisé : l’'''unité de prédiction de direction de branchement'''. C'est l'unité de prédiction de branchement qui autorise l'unité de prédiction de destination de branchement à modifier le ''program counter''. Dans certains processeurs, les deux unités sont regroupées dans le même circuit.
[[File:Relation entre prédiction de branchement et de direction de branchement.png|centre|vignette|upright=2|Relation entre prédiction de branchement et de direction de branchement.]]
==Les erreurs de prédiction de branchement==
Le processeur peut parfaitement se tromper en faisant ses prédictions, ce qui charge des instructions par erreur dans le pipeline. Dans ce cas, on parle d''''erreur de prédiction'''. Les erreurs de prédiction sont corrigées par le système d'exception précises, ou un équivalent. En clair, les instructions chargées à tord se propagent jusqu'à la sortie du pipeline, où leurs résultats sont alors annulés. N'importe quelle technique de gestion des exceptions précise fait le travail, mais les processeurs modernes utilisent un tampon de réordonnancement (''reorder buffer'') qu'on détaillera dans quelques chapitres.
Détecter les erreurs de prédiction demande d'ajouter un circuit dans le processeur : l’''unité de vérification de branchement'' (''branch verification unit''). Elle génère un indicateur d'exception, qui est géré par le système d'exception précise. Pour générer l'indicateur, elle compare l'adresse de destination prédite et celle calculée en exécutant le branchement. Pour cela, l'adresse prédite doit être propagée dans le pipeline jusqu’à ce que l'adresse de destination du branchement soit calculée.
[[File:Unité de vérification de branchement.png|centre|vignette|upright=2|Unité de vérification de branchement]]
===La pénalité de mauvaise prédiction===
Le temps pour vider le pipeline des instructions fautives dépend du pipeline. La dernière instruction à être chargée dans le pipeline le sera en même temps qu'on détecte l'erreur de prédiction. Il faudra attendre que cette instruction quitte le pipeline, et donc qu'elle traverse tous les étages. Il y a donc un temps d'attente pour collecter les résultats de ces instructions et la décision de ne pas enregistre leurs résultats dans les registres
Le nombre d'instructions chargées à tord dépend de où est détectée l'erreur de prédiction. Plus le résultat du branchement est connu tard, plus le nombre d'instructions chargées à tord sera grand, et plus le nombre de cycles d'horloge gâchés le sera aussi. Le nombre de cycles gâchés s'appelle la '''pénalité de mauvaise prédiction'''. Elle correspond précisément au nombre de cycles/étages entre le premier étage du pipeline, et l'étage où le branchement est résolu (on connait l'adresse à laquelle brancher). Plus celui-ci est éloigné du début du pipeline, plus la pénalité est importante.
Elle est généralement d'autant plus longue que le pipeline lui-même est long. Les concepteurs de processeurs font donc face à un compromis : un pipeline long permet d'exécuter plus d'instructions en parallèle, mais cela augmente aussi la pénalité de mauvaise prédiction. Un pipeline de 10-15 étapes est un bon compromis. Les pipelines plus longs ont besoin d'une prédiction de branchement élaborée pour fonctionner correctement. Plus la prédiction de branchement est performante, plus le pipeline peut être long. Les pénalités de mauvaise prédiction seront plus importantes, mais les mauvaises prédictions seront plus rares, ce qui compense complétement.
===La ''minimal control dependency''===
En utilisant une technique du nom de ''minimal control dependency'', seules les instructions qui dépendent du résultat du branchement sont supprimées du pipeline en cas de mauvaise prédiction, les autres n'étant pas annulées.
Sans ''minimal control dependency'', toutes les instructions qui suivent un branchement dans notre pipeline sont annulées. Pourtant, certaines d'entre elles pourraient être utiles. Prenons un exemple : supposons que l'on dispose d'un processeur de 31 étages (un Pentium 4 par exemple). Supposons que l'adresse du branchement est connue au 9éme étage. On fait face à un branchement qui envoie le processeur seulement 6 instructions plus loin.
Si toutes les instructions qui suivent le branchement sont supprimées, les instructions en rouge sont celles qui sont chargées incorrectement. On remarque pourtant que certaines instructions chargées sont potentiellement correctes : celles qui suivent le point d'arrivée du branchement. Elles ne le sont pas forcément : il se peut qu'elles aient des dépendances avec les instructions supprimées. Mais si elles n'en ont pas, alors ces instructions auraient dûes être exécutées.
Il serait donc plus efficace de les laisser enregistrer leurs résultats au lieu de les ré-exécuter à l’identique un peu plus tard. Ce genre de choses est possible sur les processeurs qui implémentent une technique du nom de ''Minimal Control Dependancy''. En gros, cette technique fait en sorte que seules les instructions qui dépendent du résultat du branchement soient supprimées du pipeline en cas de mauvaise prédiction.
[[File:Minimal cntrol dependency.png|centre|vignette|upright=2|Minimal control dependency]]
==La reconnaissance des branchements==
Pour prédire des branchements, le processeur doit faire la différence entre branchements et autres instructions, dès l'étage de chargement. Or, un processeur « normal », sans prédiction de branchement, ne peut faire cette différence qu'à l'étage de décodage, et pas avant. Les chercheurs ont donc dû trouver une solution.
La première solution se base sur les techniques de prédécodage vues dans le chapitre sur le cache. Pour rappel, ce prédécodage consiste à décoder partiellement les instructions lors de leur chargement dans le cache d'instructions et à mémoriser des informations utiles dans la ligne de cache. Dans le cas des branchements, les circuits de prédécodage peuvent identifier les branchements et mémoriser cette information dans la ligne de cache.
Une autre solution consiste à mémoriser les branchements déjà rencontrés dans un cache intégré dans l'unité de chargement ou de prédiction de branchement. Ce cache mémorise l'adresse (le ''program counter'') des branchements déjà rencontrés : il s'appelle le tampon d’adresse de branchement (''branch adress buffer''). À chaque cycle d'horloge, l'unité de chargement envoie le ''program counter'' en entrée du cache. S’il n'y a pas de défaut de cache, l'instruction à charger est un branchement déjà rencontré : on peut alors effectuer la prédiction de branchement. Dans le cas contraire, la prédiction de branchement n'est pas utilisée, et l'instruction est chargée normalement.
==La prédiction de l'adresse de destination==
Les explications qui vont suivre vont faire intervenir deux adresses. La première est l''''adresse de destination''' du branchement, à savoir l'adresse à laquelle le processeur doit reprendre son exécution si le branchement est pris. L'autre adresse est l''''adresse du branchement''' lui-même, l'adresse qui indique la position de l'instruction de branchement en mémoire. L'adresse du branchement est contenu dans le ''program counter'' lorsque celui-ci est chargé dans le processeur, alors que l'adresse de destination est fournie par l'unité de décodage d'instruction. Il faudra faire bien attention à ne pas confondre les deux adresses dans ce qui suit.
La '''prédiction de l'adresse de destination d'un branchement''' détermine l'adresse de destination d'un branchement. Rappelons qu'il existe plusieurs modes d'adressage pour les branchements et que chacun d'entre eux précise l'adresse de destination à sa manière. Suivant le mode d'adressage, l'adresse de destination est soit dans l'instruction elle-même (adressage direct), soit calculée à l’exécution en additionnant un ''offset'' au ''program counter'' (adressage relatif), soit dans un registre du processeur (branchement indirect), soit précisée de manière implicite (retour de fonction, adresse au sommet de la pile).
La prédiction des branchements directs et relatifs se fait globalement de la même manière, ce qui fait que nous ferons la confusion dans ce qui suit. Pour les branchements implicites, ils correspondent presque exclusivement aux instructions de retour de fonction, qui sont un cas un peu à part que nous verrons dans la section sur les branchements indirects. Pour résumer, nous allons faire une différence entre les branchements directs pour lequel l'adresse de destination est toujours la même, les branchements indirects où l'adresse de destination est variable durant l’exécution du programme, et les instructions de retour de fonction. Les branchements directs sont facilement prévisibles, vu que l'adresse vers laquelle il faut brancher est toujours la même. Pour les branchements indirects, vu que cette adresse change, la prédire celle-ci est particulièrement compliqué (quand c'est possible). Pour les instructions de retour de fonction, une prédiction parfaite est possible.
===La prédiction de l'adresse de destination pour les branchements directs===
Lorsqu'un branchement est exécuté, on peut se souvenir de l'adresse de destination et la réutiliser lors d'exécutions ultérieures du branchement. Cette technique marche à la perfection pour les branchements directs, pour lesquels cette adresse est toujours la même, mais pas pour les branchements indirects. Pour se souvenir de l'adresse de destination, on utilise un cache qui mémorise les correspondances entre l'adresse du branchement et l'adresse de destination. Il est appelé le '''tampon de destination de branchement''', ''branch target buffer'' en anglais, qui sera abrévié en BTB dans ce qui suit.
Le BTB est une amélioration du tampon d'adresse de branchement vu plus haut, auquel on aurait ajouté les adresses de destination des branchements. Le BTB est utilisé comme suit : on envoie en entrée l'adresse du branchement lors du chargement, le BTB répond au cycle suivant en précisant : s’il reconnaît l'adresse d'entrée, et quelle est l'adresse de destination si c'est le cas. Précisons que le BTB ne mémorise pas les branchements non-pris, ce qui est inutile.
Le BTB peut être un cache totalement associatif, associatif par voie, ou directement adressé, mais ces derniers sont les plus fréquents. Les BTB sont généralement des caches de type directement adressés, où seuls les bits de poids faible de l'adresse du branchement adressent le cache, le reste de l'adresse est tout simplement ignoré. Il existe aussi des BTB qui sont construits comme les caches associatifs à plusieurs voies, mais ceux-ci impliquent généralement la présence de ''tags'', ce qui fait qu'ils sont assez rares.
[[File:Branch target buffer directement adressé.png|centre|vignette|upright=2|Branch target buffer directement adressé.]]
Comme pour tous les caches, un accès au BTB peut entraîner un défaut de cache, c’est-à-dire qu'un branchement censé être dans le BTB n'y est pas. Comme pour les autres caches, les défauts de cache peuvent se classer en trois types : les défauts de cache à froid (''cold miss'') causés par la première exécution d'un branchement, les défauts liés à la capacité du BTB/cache, et les défauts par conflit d'accès au cache.
Les défauts liés à la capacité du BTB sont les plus simples à comprendre. La capacité limitée du BTB fait que d'anciens branchements sont éliminées pour laisser la place à de nouveaux, généralement en utilisant algorithme de remplacement de type LRU. En conséquence, certains branchements peuvent donner des erreurs de prédiction si leur correspondance a été éliminée du cache. On peut en réduire le nombre en augmentant la taille du BTB.
Les défauts liés aux conflits d'accès au BTB sont eux beaucoup plus intéressants, car la conception du BTB est assez spéciale dans la manière dont sont gérés ce genre de défauts. Le BTB est un cache partiellement associatif, ce qui fait que des défauts de cache par conflit d'accès peuvent avoir lieu. Pour rappel, les défauts par conflit d'accès ont lieu quand deux adresses mémoires se voient attribuer la même ligne de cache. Pour un BTB, cela correspond au cas où deux branchements se voient attribuer la même entrée dans le BTB, ce qui porte le nom d’''aliasing''. Ne pas détecter ce genre de situation sur un cache normal entraînerait des problèmes : la donnée lue/écrite ne serait pas forcément la bonne. Les caches normaux utilisent un système de tags pour éviter ce problème et on s'attendrait à ce que les BTB fassent de même. S'il existe des BTB qui utilisent des ''tags'', ils sont cependant assez rares. Pour un BTB, l'absence de ''tags'' n'est pas un problème : la seule conséquence est une augmentation du taux de mauvaises prédictions, dont les conséquences en termes de performance peuvent être facilement compensées. Les BTB se passent généralement de tags, ce qui a de nombreux avantages : le circuit est plus rapide, prend moins de portes logiques, consomme moins d'énergie, etc. Ces économies de portes logiques et de performances peuvent être utilisées pour augmenter la taille du BTB, ce qui compense, voire surcompense les mauvaises prédictions induites par l’''aliasing''.
Des processeurs se passent de BTB. À la place, ils mémorisent les correspondances entre branchement et adresse de destination dans les bits de contrôle du cache d'instructions. Cela demande de mémoriser trois paramètres : l'adresse du branchement dans la ligne de cache (stockée dans le ''branch bloc index''), l'adresse de la ligne de cache de destination, la position de l'instruction de destination dans la ligne de cache de destination. Lors de l'initialisation de la ligne de cache, on considère qu'il n'y a pas de branchement dedans. Les informations de prédiction de branchement sont ensuite mises à jour progressivement, au fur et à mesure de l’exécution de branchements. Le processeur doit en même temps charger l'instruction de destination correcte dans le cache, si un défaut de cache a lieu : il faut donc utiliser un cache multi-port. L'avantage de cette technique est que l'on peut mémoriser une information par ligne de cache, comparé à une instruction par entrée dans un tampon de destination de branchement. Mais cela ralentit fortement l'accès au cache et gaspille du circuit (les bits de contrôle ajoutés ne sont pas gratuits). En pratique, on n'utilise pas cette technique, sauf sur quelques processeurs (un des processeurs Alpha utilisait cette méthode).
===La prédiction de l'adresse de destination pour les branchements indirects et implicites===
Passons maintenant aux branchements indirects. Les techniques précédentes fonctionnement quand on les applique aux branchements indirects et ne marchent pas trop mal, mais elles se trompent à chaque fois qu'un branchement indirect change d'adresse de destination. Tous les processeurs commerciaux datant d'avant le Pentium M sont dans ce cas. De nos jours, les processeurs haute performance sont capables de prédire l'adresse de destination d'un branchement indirect. Mais même malgré ces techniques avancées de prédiction, les branchements indirects et appels de sous-programmes indirects sont souvent très mal prédits.
Les processeurs modernes utilisent souvent une '''unité de prédiction des branchements indirects''', séparée de l'unité de prédiction de branchement normale. Elle utilise un BTB amélioré, qui mémorise plusieurs adresses de destination pour un seul branchement, en compagnie d'informations pour prédire laquelle est la bonne. Il est possible d'utiliser un BTB unique, pour tous types de branchements, appelé un '''BTB unifié'''. Cette solution est illustrée ci-dessous, avec un tableau. Mais elle est tout sauf optimale, et n'est presque jamais utilisée en pratique.
{|class="wikitable"
|+ Branch Taarget Buffer unifié
|-
! Adresse du branchement !! Informations de prédiction !! Adresse de destination !! Seconde adresse de destination
|-
| 0x54462146 || 11101011 || 0x52495674 || 0x24987461
|-
| 0x54142446 || 11101101 || 0x15646956 || x0x4158746
|-
| 0x25456542 || 10011101 || 0x52495674 || 0x24157461
|-
| 0x15455855 || 11010101 || 0x52485754 || 0x24024511
|-
| ... || ... || ... || ...
|-
| ... || ... || ... || ...
|-
| 0x54462146 || 1001101 || 0x52495674 || 0x24987461
|}
Une seconde solution utilise un BTB séparé, distinct du BTB pour les branchements directs. En séparant les deux, on a un BTB simple qui ne mémorise qu'une adresse de destination par branchement, et un BTB pour branchements indirects avec plusieurs adresses de destination par branchement.
[[File:BTB avec prédiction de branchements indirects.png|centre|vignette|upright=2.5|BTB avec prédiction de branchements indirects.]]
L'idée est que l'on économise des circuits à nombre d'entrées égales. Par exemple, comparons ce que cela donne avec 64 entrées. D'un côté, on a un BTB à 64 entrées, capable de gérer deux adresse de destination par branchement. En pratique, la majorité des branchements sont des branchements directs, qui n'utiliseraient qu'une seule case mémoire. Les circuits du BTB seraient donc sous-utilisés. De l'autre, on a un BTB à 48 entrées pour branchements directs, et un second BTB avec 16 entrées, capable de mémoriser deux adresses par branchements. Le premier BTB utilise moins de circuits, et est utilisé à pleine capacité, il ne gaspille pas de circuits pour mémoriser des adresses de destination vides.
Par contre, on perd un peu en flexibilité. L'organisation précédente marche bien si 1/4 des branchements rencontrés sont des branchements indirects. Mais si le ratio est différent, la performance est légèrement sous-optimale. Attention, cela ne veut pas dire qu'il y aura des entrés vides dans un BTB, non. Simplement, le premier BTB risque de manquer de place pour les branchements directs récents, alors que les branchements indirects seront eux bien prédits, même les anciens. A l'opposé, un BTB unique permet de mémoriser les 64 derniers branchements, peu importe qu'ils soient indirects ou non. En clair : un BTB unique est plus flexible, ce qui donne un gain de performance dans certaines situations.
Cependant, cet avantage théorique ne tient pas compte de l'économie en circuits, qui peut être utilisée pour faire grossir le BTB pour branchements directs. Par exemple, à budget à transistor équivalent, on a le choix entre BTB à 64 entrées uniques, et un BTB pour branchements directs de 80 entrées avec un BTB indirect de 16 entrées. Le fait qu'un BTB soit plus gros permet des gains de performances qui compensent la perte de flexibilité. On peut ainsi avoir le meilleur des deux monde, à savoir une économie de circuits et les gains en performance qui en découlent, malgré une petite perte de flexibilité.
Certains processeurs peuvent prévoir l'adresse à laquelle il faudra reprendre lorsqu'un sous-programme a fini de s’exécuter, cette adresse de retour étant stockée sur la pile, ou dans des registres spéciaux du processeur dans certains cas particuliers. Ils possèdent un circuit spécialisé capable de prédire cette adresse : la '''prédiction de retour de fonction''' (''return function predictor''). Lorsqu'une fonction est appelée, ce circuit stocke l'adresse de retour d'une fonction dans des registres internes au processeur, organisés en pile. Avec cette organisation des registres en forme de pile, on sait d'avance que l'adresse de retour du sous-programme en cours d'exécution est au sommet de cette pile.
==La prédiction de branchement==
La prédiction de branchement tente de prédire si un branchement sera pris ou non-pris et décide d'agir en fonction. Si on prédit qu'un branchement est non pris, on continue l’exécution à partir de l'instruction qui suit le branchement. À l'inverse si le branchement est prédit comme étant pris, le processeur devra recourir à l'unité de prédiction de direction de branchement. Maintenant, voyons comment le processeur fait pour prédire si un branchement est pris ou non. La prédiction de branchement se base avant tout sur des statistiques, c’est-à-dire qu'elle détermine la probabilité d’exécution d'un branchement en fonction d'informations connues au moment de l’exécution d'un branchement. Elle assigne une probabilité qu'un soit branchement soit pris en fonction de ces informations : si la probabilité est de plus de 50%, le branchement est considéré comme pris.
===Les contraintes d’implémentation de la prédiction de branchement===
La prédiction de branchement n'a d'intérêt que si les prédictions sont suffisamment fiables pour valoir le coup. Rappelons que la pénalité lors d'une mauvaise prédiction est importante : vider le pipeline est une opération d'autant plus coûteuse que le pipeline est long. Et plus cette pénalité est importante, plus le taux de réussite de l'unité de prédiction doit être important. En conséquence, une simple prédiction fiable à 50% ne tiendra pas la route et il est généralement admis qu'il faut au minimum un taux de réussite proche de 90% de bonnes prédictions, voire plus sur les processeurs modernes. Plus la pénalité en cas de mauvaise prédiction est importante, plus le taux de bonnes prédiction doit être élevé. Heureusement, la plupart des branchements sont des '''branchements biaisés''', c’est-à-dire qu'ils sont presque toujours pris ou presque toujours non-pris, sauf en de rares occasions. De tels branchements font que la prédiction des branchements est facile, bien qu'ils posent paradoxalement quelques problèmes avec certaines techniques, comme nous le verrons plus bas.
Un autre point important est que les unités de prédiction de branchement doivent être très rapides. Idéalement, elles doivent fournir leur prédiction en un seul cycle d'horloge. En conséquence, elles doivent être très simples, utiliser des calculs rudimentaires et demander peu de circuits. Un seul cycle d'horloge est un temps très court, surtout sur les ordinateurs avec une fréquence élevée, chose qui est la norme sur les processeurs à haute performance. Les techniques de prédiction dynamique ne peuvent donc pas utiliser des méthodes statistiques extraordinairement complexes. Cela va à l'encontre du fait que le tau de mauvaises prédictions doit être très faible, de préférence inférieur à 10% dans le meilleur des cas, idéalement inférieur à 1% sur les processeurs modernes. Vous comprenez donc aisément que concevoir une unité de prédiction de branchement est un véritable défi d’ingénierie électronique qui requiert des prouesses technologiques sans précédent.
===La classification des techniques de prédiction de branchement===
Suivant la nature des informations utilisées, on peut distinguer plusieurs types de prédiction de branchement : locale, globale, dynamique, statique, etc.
Il faut aussi distinguer la prédiction de branchements statique de la prédiction dynamique. Avec la '''prédiction statique''', on prédit si le branchement est pris ou non en fonction de ses caractéristiques propres, comme son adresse de destination, la position du branchement en mémoire, le mode d'adressage utilisé, si le branchement est direct ou indirect, ou toute autre information encodée dans le branchement lui-même. Les informations utilisées sont disponibles dans le programme exécuté, et ne dépendent pas de ce qui se passe lors de l’exécution du programme en lui-même. Par contre, avec la '''prédiction dynamique''', des informations qui ne sont disponibles qu'à l’exécution sont utilisées pour faire la prédiction. Typiquement, la prédiction dynamique extrait des statistiques sur l’exécution des branchements, qui sont utilisées pour faire la prédiction. Les statistiques en question peuvent être très simples : une simple moyenne sur les exécutions précédentes donne déjà de bons résultats. Mais les techniques récentes effectuent des opérations statistiques assez complexes, comme nous le verrons plus bas.
Pour ce qui est de la prédiction dynamique, il faut distinguer la prédiction de branchement dynamique locale et globale. La '''prédiction locale''' sépare les informations de chaque branchement, alors que la '''prédiction globale''' fusionne les informations pour tous les branchements et fait des moyennes globales. Les deux méthodes ont leurs avantages et leurs inconvénients, car elles n'utilisent pas les mêmes informations. La prédiction locale seule ne permet pas d'exploiter les corrélations entre branchements, à savoir que le résultat d'un branchement dépend souvent du résultat des autres, alors que la prédiction globale le peut. À l'inverse, la prédiction globale seule n'exploite pas des informations statistiques précise pour chaque branchement. Idéalement, les deux méthodes sont complémentaires, ce qui fait qu'il existe des prédicteurs de branchement hybrides, qui exploitent à la fois les statistiques pour chaque branchement et les corrélations entre branchements. Les prédicteurs hybrides ont de meilleures performances que les prédicteurs purement globaux ou locaux.
===La prédiction statique de branchement===
Avec la '''prédiction statique''', on prédit le résultat du branchement en fonction de certaines caractéristiques du branchement lui-même, comme son adresse, son mode d'adressage, l'adresse de destination connue, etc.
Dans son implémentation la plus explicite, la prédiction est inscrite dans le branchement lui-même. Quelques bits de l'opcode du branchement précisent si le branchement est majoritairement pris ou non pris. Ils permettent d'influencer les règles de prédiction de branchement et de passer outre les réglages par défaut. Ces bits sont appelés des '''suggestions de branchement''' (''branch hint''). Mais tous les processeurs ne gèrent pas cette fonctionnalité. Et de plus, cette solution marche assez mal. La raison est que le programmeur ou le compilateur doit déterminer si le branchement est souvent pris ou non-pris, mais qu'il n'a pas de moyen réellement fiable pour cela. Une solution possible est d’exécuter le programme sur un ensemble de données réalistes et d'analyser le comportement de chaque branchement, afin de déterminer les suggestions de branchement adéquates, mais c'est une solution lourde et peu pratique, qui a de bonnes chances de donner des résultats peu reproductibles d'une exécution à l'autre.
Une autre idée part de la distinction entre les branchements inconditionnels toujours pris, et les branchements conditionnels au résultat variable. Ainsi, on peut donner un premier algorithme de prédiction statique : les branchements inconditionnels sont toujours pris alors que les branchements conditionnels ne sont jamais pris (ce qui est une approximation). Cette méthode est particulièrement inefficace pour les branchements de boucle, où la condition est toujours vraie, sauf en sortie de boucle ! Il a donc fallu affiner légèrement l'algorithme de prédiction statique.
Une autre manière d’implémenter la prédiction statique de branchement est de faire une différence entre les branchements conditionnels ascendants et les branchements conditionnels descendants. Un branchement conditionnel ascendant est un branchement qui demande au processeur de reprendre plus loin dans la mémoire : l'adresse de destination est supérieure à l'adresse du branchement. Un branchement conditionnel descendant a une adresse de destination inférieure à l'adresse du branchement : le branchement demande au processeur de reprendre plus loin dans la mémoire. Les branchements ascendants sont rarement pris (ils servent dans les conditions de type SI…ALORS), contrairement aux branchements descendants (qui servent à fabriquer des boucles). On peut ainsi modifier l’algorithme de prédiction statique comme suit :
* les branchements inconditionnels sont toujours pris ;
* les branchements descendants sont toujours pris ;
* les branchements ascendants ne sont jamais pris.
===La prédiction de branchement avec des compteurs à saturation===
Les méthodes de prédiction de branchement statique sont intéressantes, mais elles montrent rapidement leurs limites. La prédiction dynamique de branchement donne de meilleures résultats. Sa version la plus simple se contente de mémoriser ce qui s'est passé lors de l’exécution précédente du branchement. Si le branchement a été pris, alors on suppose qu'il sera pris la prochaine fois. De même, s'il n'a pas été pris, on suppose qu'il ne le sera pas lors de la prochaine exécution. Pour cela, chaque entrée du BTB est associée à un bit qui mémorise le résultat de la dernière exécution du branchement : 1 s'il a été pris, 0 s'il n'a pas été pris. C'est ce qu'on appelle la '''prédiction de branchements sur 1 bit'''. Cette méthode marche bien pour les branchements des boucles (pas ceux dans la boucle, mais ceux qui font se répéter les instructions de la boucle), ainsi que pour les branchements inconditionnels, mais elle échoue assez souvent pour le reste. Sa performance est généralement assez faible, malgré son avantage pour les boucles. Il faut dire que les pénalités en cas de mauvaise prédiction sont telles qu'une unité de prédiction de branchement doit avoir un taux de succès très élevé pour être utilisable ne pratique, et ce n'est pas le cas de cette technique.
Une version améliorée calcule, pour chaque branchement, d'une moyenne sur les exécutions précédentes. Typiquement, on mémorise à chaque exécution du branchement si celui-ci est pris ou pas, et on effectue une moyenne statistique sur toutes les exécutions précédentes du branchement. Si la probabilité d'être pris est supérieure à 50 %, le branchement est considéré comme pris. Dans le cas contraire, il est considéré comme non pris. Pour cela, on utilise un '''compteur à saturation''' qui mémorise le nombre de fois qu'un branchement est pris ou non pris. Ce compteur est initialisé de manière à avoir une probabilité proche de 50 %. Le compteur est incrémenté si le branchement est pris et décrémenté s'il est non pris. Pour faire la prédiction, on regarde le bit de poids fort du compteur : le branchement est considéré comme pris si ce bit de poids fort est à 1, et non pris s'il vaut 0. Pour la culture générale, il faut savoir que le compteur à saturation du Pentium 1 était légèrement bogué. La plupart des processeurs qui utilisent cette technique ont un compteur à saturation par entrée dans le tampon de destination de branchement, donc un compteur à saturation par branchement.
[[File:Compteur à saturation.png|centre|vignette|upright=2|Compteur à saturation.]]
Rappelons que chaque compteur est associé à une entrée du BTB. Ce qui fait que le phénomène d’''aliasing'' mentionné plus haut peut perturber les prédictions de branchement. Concrètement, deux branchements peuvent se voir associés à la même entrée du BTB, et donc au même compteur à saturation. Cependant, cet ''aliasing'' entraine l'apparition d''''interférences entre branchements''', à savoir que les deux branchements vont agir sur le même compteur à saturation. L'interférence peut avoir des effets positifs, neutres ou négatifs, suivant la corrélation entre les deux branchements. L’''aliasing'' n'est pas un problème si les deux branchements sont fortement corrélés, c’est-à-dire que les deux sont souvent pris ou au contraire que les deux sont très souvent non-pris. Dans ce cas, les deux branchements vont dans le même sens et l'interférence est neutre, voire légèrement positive. Mais si l'un est souvent pris et l’autre souvent non-pris, alors l'interférence est négative. En conséquence, les deux branchements vont se marcher dessus sans vergogne, chacun interférant sur les prédictions de l'autre ! Il est rare que l’''aliasing'' ait un impact significatif sur les unités de prédiction des deux paragraphes précédents. Il se manifeste surtout quand la BTB est très petite et la seule solution est d'augmenter la taille du BTB.
===La prédiction de branchement à deux niveaux avec un historique global===
La prédiction par historique conserve le résultat des branchements précédents dans un '''registre d'historique''', qui mémorise le résultat des N branchements précédents (pour un registre de N bits). Ce registre d'historique est un registre à décalage mis à jour à chaque exécution d'un branchement : on fait rentrer un 1 si le branchement est pris, et un 0 sinon. Une unité de prédiction globale utilise cet historique pour faire sa prédiction, ce qui tient compte d'éventuelles corrélations entre branchements consécutifs/proches pour faire les prédictions. C'est un avantage pour les applications qui enchaînent des if...else ou qui contiennent beaucoup de if...else imbriqués les uns dans les autres, qui s’exécutent plusieurs fois de suite. Le résultat de chaque if...else dépend généralement des précédents, ce qui rend l'utilisation d'un historique global intéressant. Par contre, ils sont assez mauvais pour le reste des branchements. Mais cette qualité devient un défaut quand elle détecte des corrélations fortuites ou parasites, inutiles pour la prédiction (corrélation n'est pas causalité, même pour prédire des branchements). Du fait de ce défaut, la prédiction globale a besoin de registres d'historiques globaux très larges, de plusieurs centaines de bits au mieux.
Dans les '''unités de prédiction à deux niveaux''', le registre d'historique est combiné avec des compteurs à saturation d'une autre manière. Il n'y a pas un ou plusieurs compteurs à saturation par branchement, l'ensemble est organisé différemment. Les compteurs à saturation sont regroupés dans une ou plusieurs '''''Pattern History Table''''', que nous noterons PHT dans ce qui suit. En clair, on a un registre d'historique, suivi par une ou plusieurs PHT, ce qui fait que de telles unités de prédiction de branchement sont appelées des '''unités de prédiction de branchement à deux niveaux'''. Il peut y avoir soit une PHT unique, appelée '''PHT globale''', où une PHT associée chaque branchement, ce qui s'appelle une '''PHT locale'''. Mais laissons ce côté ce détail pour le moment. Sachez juste qu'il est parfaitement possible d'avoir des unités de prédiction avec plusieurs PHTs, ce qui sera utile pour la suite. Pour le moment, nous allons nous concentrer sur les unités avec une seule PHT couplé à un seul registre d'historique.
[[File:2 level branch predictor.svg|centre|vignette|upright=2|Unités de prédiction de branchement à deux niveaux avec une seule PHT.]]
L'unité de prédiction à deux niveaux la plus simple utilise un registre d'historique global, couplé à une PHT unique (une PHT globale). Pour un registre d'historique global unique de n bits, la PHT globale contient 2^n compteurs à saturation, un par valeur possible de l'historique. Pour chaque valeur possible du registre d'historique, la PHT associée contient un compteur à saturation dont la valeur indique si le prochain branchement sera pris ou non-pris. Le choix du compteur à saturation utiliser se fait grâce au registre d'historique global, et éventuellement avec d'autres informations annexes.
[[File:Prédiction dynamique à deux niveaux.png|centre|vignette|upright=2|Prédiction dynamique à deux niveaux.]]
Le circuit précédent a cependant un problème : si deux branchements différents s'exécutent et que l'historique est le même, le processeur n'y verra que du feu. Il y a alors une interférence, à savoir que les deux branchements vont agir sur le même compteur à saturation. On se retrouve alors dans une situation d’''aliasing'', conceptuellement identique à l’''aliasing'' dans le BTB ou avec des compteurs à saturation. Sauf que les interférences sont alors beaucoup plus nombreuses et qu'elles sont clairement un problème pour les performances ! Et en réduire le nombre devient alors un problème bien plus important qu'avec les unités de prédiction précédentes. Si réduire l'''aliasing'' avec de simples compteurs à saturation demandait d'augmenter la taille de la PHT, d'autres solutions sont possibles sur les unités de prédiction globales. Elles sont au nombre de deux : mitiger les interférences liées aux branchements biaisés, ajouter des informations qui discriminent les branchements. Voyons ces solutions dans le détail.
====La mitigation des interférences par usage de l'adresse de branchement====
La première solution pour réduire l'impact de l’''aliasing'' est d'augmenter la taille de la PHT et de l'historique. Plus l'historique et la PHT sont grands, moins l’''aliasing'' est fréquent. Mais avoir des historiques et des PHT de très grande taille n'est pas une mince affaire et le cout en termes de performances et de portes logiques est souvent trop lourd. Une autre solution consiste à utiliser, en plus de l'historique, des informations sur le branchement lui-même pour choisir le compteur à saturation à utiliser. Typiquement, on peut utiliser l'adresse du branchement en plus de l'historique, pour choisir le compteur à saturation adéquat.
Pour être précis, on n'utilise que rarement l'adresse complète du branchement, mais seulement les bits de poids faible. La raison est que cela fait moins de bits à utiliser, donc moins de circuits et de meilleures performances. Mais cela fait que l’''aliasing'' est atténué, pas éliminé. Des branchements différents ont beau avoir des adresses différentes, les bits de poids faible de leurs adresses peuvent être identiques. Des confusions sont donc possibles, mais l'usage de l'adresse de branchement réduit cependant l’''aliasing'' suffisamment pour que cela suffise en pratique.
Une unité de prédiction de ce type est illustrée ci-dessous. Sur cette unité de prédiction, le choix de la PHT est réalisé par l'historique, alors que le choix du compteur à saturation est réalisé par l'adresse du branchement. Pour concevoir le circuit, on part d'une unité de prédiction de branchement basée sur des compteurs à saturation, avec un compteur à saturation par branchement, sauf qu'on copie les compteurs à saturation en autant d'exemplaires que de valeurs possibles de l'historique. Par exemple, pour un historique de 4 bits, on a 16 valeurs différentes possibles pour l'historique, donc 16 PHT et donc 16 compteurs à saturation par branchement. Chaque compteur à saturation mémorise la probabilité que le branchement associé soit pris, mais seulement pour la valeur de l'historique associée. Le choix de la PHT utilisée pour la prédiction est réalisé par l'historique global, qui commande un multiplexeur relié aux PHT. Les compteurs à saturation d'un branchement sont mis à jour seulement quand le branchement s'est chargé pendant que l'historique associé était dans le registre d'historique. Cette méthode a cependant le défaut de gâcher énormément de transistors.
[[File:Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des compteurs à saturation sélectionnés par l'historique global]]
D'autres unités de prédiction fonctionnent sur le principe inverse de l'unité précédente. Sur les unités de prédiction qui vont suivre, le choix d'une PHT est réalisé par l'adresse du branchement, alors que le choix du compteur dans la PHT est réalisé par l'historique. C'est ce que font les unités de prédiction '''''gshare''''' et '''''gselect''''', ainsi que leurs dérivées.
Avec les unités de prédiction '''''gselect''''', on concatène les bits de poids faible de l'adresse du branchement et l'historique pour obtenir le numéro du compteur à saturation à utiliser. Avec cette technique, on utilise une PHT unique, mais il est possible de découper celle-ci en plusieurs PHT. On peut par exemple utiliser une PHT par branchement/entrée du BTB. Ou alors, on peut utiliser une PHT apr valeur possible de l'historique, ce qui fait qu'on retrouve l'unité de prédiction précédente.
[[File:Prédiction « gselect ».png|centre|vignette|upright=2|Prédiction « gselect ».]]
Avec les unités de prédiction '''''gshare''''', on fait un XOR entre le registre d'historique et les bits de poids faible de l'adresse du branchement. Le résultat de ce XOR donne le numéro du compteur à utiliser. Avec cette technique, on utilise une PHT globale et un historique global, mais le registre d'historique est manipulé de manière à limiter l'''aliasing''.
[[File:Prédiction « gshare ».png|centre|vignette|upright=2|Prédiction « gshare ».]]
Intuitivement, on pourrait croire que les unités de prédiction gselect ont de meilleures performances. C'est vrai que les unités gshare combinent l'adresse du branchement et l'historique d'une manière qui fait perdre un peu d'information (impossible de retrouver l'adresse du branchement et l'historique à partir du résultat du XOR), alors que les unités gselect conservent ces informations. Mais il faut prendre en compte le fait que les deux unités doivent se comparer à PHT identique, de même taille. Prenons par exemple une PHT de 256 compteurs, adressée par 8 bits. Avec une unité gselect, les 8 bits sont utilisés à la fois pour l'historique et pour les bits de poids faible de l'adresse du branchement. Alors qu'avec une unité gshare, on peut utiliser un historique de 8 bits et les 8 bits de poids faible de l'adresse du branchement. L'augmentation de la taille de l'historique et du nombre de bits utilisés fait que l’''aliasing'' est réduit, et cela compense le fait que l'on fait un XOR au lieu d'une concaténation.
====La mitigation des interférences par la structuration en caches des PHTs====
Les techniques précédentes utilisent les bits de poids faible de l'adresse pour sélectionner la PHT adéquate, ou pour sélectionner le compteur dans la PHT. Mais le reste de l'adresse n'est pas utilisé. Cependant, il est théoriquement possible de conserver les bits de poids fort, afin d'identifier le branchement associé à un compteur. Pour cela, chaque compteur à saturation se voit associé à un ''tag'', comme pour les mémoires caches. Ce dernier mémorise le reste de l'adresse du branchement associé au compteur. Lorsque l'unité de prédiction démarre une prédiction, elle récupère les bits de poids fort de l'adresse du branchement et compare ceux-ci avec le ''tag''. S’il y a un ''match'', c'est signe que le compteur est bien associé au branchement à prédire. Dans le cas contraire, c'est signe qu'on fait face à une situation d’''aliasing'' et le compteur n'est pas utilisé pour la prédiction. Le résultat transforme la PHT en une sorte de cache assez particulier, qui associe un compteur à saturation à chaque couple adresse-historique.
La technique du paragraphe précédent peut être encore améliorée afin de réduire l’''aliasing''. L’''aliasing'' sur une unité de prédiction de branchement est similaire aux conflits d'accès au cache, où deux données/adresses atterrissent dans la même ligne de cache. Une PHT peut formellement être vue comme un cache directement adressé. Une solution pour limiter ces conflits d'accès à de tels, est de les transformer en un cache associatif à n voies. On peut faire la même chose avec les unités de prédiction de branchement. On peut dupliquer les PHT, de manière à ce que les bits de poids faible de l'adresse se voient attribuer à plusieurs compteurs à saturation, un par branchement possible. Ce faisant, on réduit les interférences entre branchements. Mais cette technique augmente la quantité de circuits utilisés et la consommation d'énergie, ainsi que le temps d'accès, sans compter qu'elle requiert d'utilisation de ''tags'' pour fonctionner à la perfection. Pour les unités de prédiction de branchement, les mesures à ce sujet ne semblent pas montrer un impact réellement important sur les performances, comparé à une organisation de type directement adressé.
Il est possible de pousser la logique encore plus loin en ajoutant des caches de victime et d'autres mécanismes courant sur les caches usuels.
====La mitigation des interférences liées aux branchements biaisés====
D'autres techniques limitent encore plus l’''aliasing'' en tenant compte de certains points liés au biais des branchements. L’''aliasing'' a un impact négatif quand deux branchements aliasés (qui sont attribués au même compteur à saturation) vont souvent dans des sens différents : si l'un est non-pris, l'autre a de bonnes chances d'être pris. De plus, la plupart des branchements sont biaisés (presque toujours pris ou presque toujours non-pris, à plus de 90/99% de chances) ou sont du moins très fortement orientés dans une direction qu'une autre. Plus un branchement a une probabilité élevée d' être pris ou non-pris, plus sa capacité à interférer avec les autres est forte. En conséquence, les branchements biaisés ou quasi biaisés sont les plus problématiques pour l’''aliasing''. Or, il est possible de concevoir des prédicteurs de branchements qui atténuent fortement les interférences des branchements biaisés ou quasi biaisés. C’est le cas de l’''agree predictor'' et de l'unité de prédiction bimodale, que nous allons voir dans ce qui suit.
La '''prédiction par consensus''' (''agree predictor'') combine une unité de prédiction à historique global (idéalement de type gshare ou gselect, mais une unité normale marche aussi) et avec une unité de prédiction à un bit qui indique la direction privilégiée du branchement. L'unité de prédiction à un bit est en quelque sorte fusionnée avec le BTB. Le BTB contient, pour chaque branchement, un compteur à saturation de 1 bit qui indique si celui-ci est généralement pris ou non-pris. L'unité à historique global a un fonctionnement changé : elle calcule non pas la probabilité que le branchement soit pris ou non-pris, mais la probabilité que le résultat du branchement soit compatible avec le résultat de l'unité de prédiction de 1 bit. La prédiction finale vérifie que ces deux circuits sont d'accord, en faisant un NXOR entre les résultats des deux unités de prédiction de branchement. Des calculs simples de probabilités montrent que l'''agree predictor'' a des résultats assez importants. Ses résultats sont d'autant meilleurs que les branchements sont biaisés et penchent fortement vers le côté pris ou non-pris.
[[File:Prédiction par consensus.png|centre|vignette|upright=2|Prédiction par consensus.]]
L''''unité de prédiction bimodale''' part d'une unité gshare, mais sépare la PHT globale en deux : une PHT dédiée aux branchements qui sont très souvent pris et une autre pour les branchements très peu pris. Les branchements très souvent pris vont interférer très fortement avec les branchements très souvent non-pris, et inversement. Par contre, les branchements très souvent pris n'interféreront pas entre eux, de même que les branchements non-pris iront bien ensemble. D'où l'idée de séparer ces deux types de branchements dans des PHT séparées, pour éviter le mauvais ''aliasing''. Les deux PHT fournissent chacune une prédiction. La bonne prédiction est choisie par un multiplexeur commandé par une unité de sélection, qui se charge de déduire quelle unité a raison. Cette unité de sélection est une unité de prédiction basée sur des compteurs à saturation de 2bits. On peut encore améliorer l'unité de sélection de prédiction en n'utilisant non pas des compteurs à saturation, mais une unité de prédiction dynamique à deux niveaux, ou toute autre unité vue auparavant.
[[File:Prédiction bimodale.jpg|centre|vignette|upright=2|Prédiction bimodale.]]
===Les unités de prédiction à deux niveaux locales===
Une autre solution pour éliminer l’''aliasing'' est d'utiliser une PHT par branchement. C’est contre-intuitif, car on se dit que l'usage d'un registre d'historique global va avec une PHT unique, mais il y a des contre-exemples où un historique global est associé à plusieurs PHT ! Dans ce cas, chaque PHT est associée à un branchement, ce qui leur vaut le nom de '''PHT locale''', à l'opposé d'une PHT unique appelée ''PHT globale''. L'intérêt est de faire des prédictions faites sur mesure pour chaque branchement. Par exemple, le branchement X sera prédit comme pris, alors que le branchement Y sera non-pris, pour un historique global identique. La PHT à utiliser est choisie en fonction d'informations qui dépendent du branchement, typiquement les bits de poids faible de l'adresse du branchement. De telles unités fonctionnent comme suit : on choisit une PHT en fonction du branchement, puis le compteur à saturation est choisi dans cette PHT par le registre d'historique global. C'est conceptuellement la méthode utilisée sur les unités gselect, mais avec une implémentation différente. L'''aliasing'' est aussi fortement réduit, bien que cette réduction soit semblable à celle obtenue avec les méthodes précédentes.
[[File:Unité de prédiction de branchement avec des PHT locales et un historique global.jpg|centre|vignette|upright=2|Unité de prédiction de branchement avec des PHT locales et un historique global]]
On peut aller plus loin et utiliser non seulement des PHT locales, mais aussi faire quelque chose d'équivalent sur l'historique. Au lieu d'utiliser un historique global, pour tous les branchements, on peut utiliser un historique par branchement. Concrètement, là où les méthodes précédentes mémorisaient le résultat des n derniers branchements, les méthodes qui vont suivre mémorisent, pour chaque branchement dans le BTB, le résultat des n dernières exécutions du branchement. Par exemple, si le registre d'historique contient 010, cela veut dire que le branchement a été non pris, puis pris, puis non pris. L'information mémorisée dans l'historique est alors totalement différente. Il y a un historique par entrée du BTB, soit un historique par branchement connu du processeur. Combiner PHT et historiques locaux donne une '''unité de prédiction à deux niveaux locale'''. Elle utilise des registres d'historique locaux de n bits, chacun étant couplé avec sa propre PHT locale contenant 2^n compteurs à saturation. Quand un branchement est exécuté, la PHT adéquate est sélectionnée, le registre d'historique de ce branchement est sélectionné, et l'historique local indique quel compteur à saturation choisir dans la PHT locale.
[[File:Unité de prédiction à deux niveaux purement locale.png|centre|vignette|upright=2.5|Unité de prédiction à deux niveaux avec des historiques et des PHT locales.]]
Implémenter cette technique demande d'ajouter un circuit qui sélectionne l'historique et la PHT adéquate en fonction du branchement. Le choix de la PHT et de l'historique se fait idéalement à partir de l'adresse du branchement. Mais c'est là une implémentation idéale, qui demande beaucoup de circuits pour un pouvoir de discrimination extrême. Généralement, l'unité de prédiction n'utilise que les bits de poids faible de l'adresse du branchement, ce qui permet de faire un choix correct, mais avec une possibilité d'''aliasing''. Pour récupérer l'historique local voulu, les bits de poids faible adressent une mémoire RAM, dont chaque byte contient l'historique local associé. La RAM en question est appelée la '''''Branch History Table''''', ou encore la '''table des historiques locaux''', que nous noterons BHT dans ce qui suivra. L'historique local est ensuite envoyé à la PHT adéquate, choisie en fonction des mêmes bits de poids faible du PC. Un bon moyen pour cela est d'accéder à toutes les PHT en parallèle, mais de sélectionner la bonne avec un multiplexeur, ce dernier étant commandé par les bits de poids faible du PC.
[[File:Table des historiques locaux.jpg|centre|vignette|upright=2|Table des historiques locaux]]
Cette unité de prédiction peut correctement prédire des branchements mal prédits par des compteurs à saturation. Tel est le cas des branchements dont le résultat est le suivant : pris, non pris, pris, non pris, et ainsi de suite. Même chose pour un branchement qui ferait : pris, non pris, non pris, non pris, pris, non pris, non pris, non pris, etc. En clair, il s'agit de situations où l'historique d'un branchement montre un ''pattern'', un motif qui se répète dans le temps. Notons que l'apparition de tels motifs correspond le plus souvent à la présence de boucles dans le programme. Quand une boucle s’exécute, les branchements qui sont à l'intérieur tendent à exprimer de tels motifs. Avec de tels motifs, les compteurs à saturation feront des prédictions incorrectes, là où les prédicteurs avec registre d'historique feront des prédictions parfaites. Par contre, elles sont assez mauvaises pour les autres types de branchements.
Les boucles étant très fréquentes dans de nombreux programmes, de telles unités de prédiction donnent généralement de bons résultats.
Un défaut des unités de prédiction purement locales de ce type est leur cout en termes de circuits. Utiliser un registre d'historique et une PHT par branchement a un cout élevé. Elles utilisent beaucoup de transistors, consomment beaucoup de courant, chauffent beaucoup, prennent beaucoup de place. Elles ont aussi des temps de calcul un peu plus important que les unités purement globales, mais pas de manière flagrante. De plus, les mesures montrent que l'historique global donne de meilleures prédictions que l'historique local, quand on regarde l'ensemble des branchements, mais à condition qu'on utilise des historiques globaux très longs, difficiles à mettre en pratique. Autant dire que les unités de prédiction avec un historique purement local sont rarement utilisées. Les gains en performance avec un historique local s'observent surtout pour les branchements qui sont dans des boucles ou pour les branchements utilisés pour concevoir des boucles, alors que l'implémentation globale marche bien pour les if..else (surtout quand ils sont imbriqués). Les deux sont donc complémentaires. Dans les faits, elles sont rarement utilisées du fait de leurs défauts, au profit d'unités de prédiction hybrides, qui mélangent historiques et PHT locaux et globaux.
===Les unités de prédiction à deux niveaux mixtes (globale/locale)===
Nous venons de voir les unités de prédiction de branchement à deux niveaux, aussi bien les implémentations avec un historique global (pour tous les branchements) et des historiques locaux (un par branchement). L'implémentation globale utilise un seul registre d'historique pour tous les branchements, alors que l’implémentation locale connaît l'historique de chaque branchement. Il est admis que l'historique global donne de meilleure prédiction que l'historique local. Mais les deux informations, historique global et historique de chaque branchement, sont des informations complémentaires, ce qui fait qu'il existe des approches hybrides. Certaines unités de prédiction utilisent les deux pour faire leur prédiction.
Par exemple, on peut citer la '''prédiction mixte''' (''alloyed predictor''). Avec celle-ci, la table des compteurs à saturation est adressée avec la concaténation : de bits de l'adresse du branchement, du registre d'historique global et du registre d'historique local adressé par l'adresse du branchement.
[[File:Prédiction mixte.jpg|centre|vignette|upright=2|Prédiction mixte.]]
Une version optimisée de ce circuit permet d’accéder à la PHT et à la BHT en parallèle. Avec elle les bits de poids faible de l'adresse du branchement sont concaténés avec l'historique global. Le résultat est ensuite envoyé à plusieurs PHT locales distinctes, en parallèle. Les différences PHT envoient leurs résultats à un multiplexeur, commandé par l'historique local, qui sélectionne le résultat adéquat. L'inconvénient de cette mise en œuvre est que le circuit est assez gros, notamment en raison de la présence de plusieurs PHT séparées.
[[File:Alloyed predictor optimisé.jpg|centre|vignette|upright=2|''Alloyed predictor'' optimisé]]
===La prédiction de branchement avec des perceptrons===
Les méthodes précédentes utilisent de un ou plusieurs comparateurs à saturation par historique possible. Sachant qu'il y a 2^n valeurs possibles pour un historique de n bits, le nombre de compteurs à saturation augmente exponentiellement avec la taille de l'historique. Cela limite grandement la taille de l'historique, alors qu'on aurait besoin d'un historique assez grand pour faire des prédictions excellentes. Pour éviter cette explosion exponentielle, des alternatives basées sur des techniques d'apprentissage artificiel (''machine learning'') existent. Un exemple est celui des unités de prédiction de branchement des processeurs AMD actuels se basent sur des techniques dites de ''neural branch prediction'', basée sur des perceptrons.
La quasi-totalité des unités de prédiction de ce type se basent sur des perceptrons, un algorithme d'apprentissage automatique très simple, que nous aborderons plus bas. En théorie, il est possible d'utiliser des techniques d'apprentissage automatiques plus élaborées, comme les techniques de ''back-propagation'', mais cela n'est pas possible en pratique. Rappelons qu'une unité de prédiction de branchement doit idéalement fournir sa prédiction en un cycle d'horloge, et qu'un temps de calcul de la prédiction de 2 à 3 cycles est déjà un problème. L'implémentation d'un perceptron est déjà problématique de ce point de vue, car elle demande d'utiliser beaucoup de circuits arithmétiques complexes (multiplication et addition). Autant dire que les autres techniques usuelles de ''machine learning'', encore plus gourmandes en calculs, ne sont pas praticables.
====Les perceptrons et leur usage en tant qu'unité de prédiction de branchements====
L'idée derrière l'utilisation d'un perceptron est que l'historique est certes utile pour prédire si un branchement sera pris ou non, mais certains bits de l'historique sont censés être plus importants que d'autres. En soi, ce genre de situation est réaliste. Il arrive que certains branchements soient pris uniquement si les deux branchements précédents ne le sont pas, ou que si l'avant-dernier branchement est lui aussi pris. Mais les autres résultats de branchement présents dans l'historique ne sont pas ou peu utiles. En conséquence, deux historiques semblables, qui ne sont différents que d'un ou deux bits, peuvent donner des prédictions presque identiques. Dans ce cas, on peut faire la prédiction en n'utilisant que les bits identiques entre ces historiques, ou du moins en leur donnant plus d'importance qu'aux autres. On exploite alors des corrélations avec certains bits de l'historique, plutôt qu'avec l'historique entier lui-même.
Pour coder ces corrélations, on associe un coefficient à chaque bit de l'historique, qui indique à quel point ce bit est important pour déterminer le résultat final. Ce coefficient est proportionnel à la corrélation entre le résultat du branchement associé au bit de l'historique, et le branchement à prédire. Notons que ces coefficients sont des nombres entiers qui peuvent être positifs, nuls ou négatifs. Un coefficient positif signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances de l'être aussi (et inversement). Un coefficient nul signifie que les deux branchements sont indépendants et que le bit de l'historique associé n'est pas à prendre en compte. Pour un coefficient négatif, cela signifie que si le branchement associé a été pris, alors le branchement à prédire a de bonnes chances d'être non-pris (et inversement). Les coefficients en question sont généralement compris entre -1 et 1.
Une fois qu'on connait ces coefficients de corrélations, on peut calculer la probabilité que le branchement à prédire soit pris, avec des calculs arithmétiques simples. Par contre, cela demande que l'interprétation de l'historique soit modifiée. L'historique est toujours codé avec des bits, avec un 1 pour un branchement pris et un 0 pour un branchement non-pris. Mais dans les calculs qui vont suivre, un branchement pris est codé par un 1, alors qu'un branchement non-pris est codé par un -1 ! Ce détail permet de simplifier grandement les calculs. Dans sa version la plus simple, le perceptron calcule un nombre Z qui est positif ou nul si le branchement est pris, négatif s'il ne l'est pas. Tous les coefficients <math>p_i</math> sont alors des nombres relatifs, pouvant être positifs, nuls ou négatifs. Ils sont généralement compris entre -1 et 1. La formule devient donc celle-ci :
: <math>Z = w_0 + \sum_i (h_i \times w_i)</math>
[[File:ArtificialNeuronModel francais.png|centre|vignette|upright=2|Perceptron.]]
: Sur le plan mathématique, le perceptron effectue un produit scalaire entre deux vecteurs : l'historique et le vecteur des poids.
Choisir les coefficients adéquats est le but de l'algorithme du perceptron. L'unité de prédiction part de poids par défaut, qui sont mis à jour à chaque bonne ou mauvaise prédiction. Toute la magie de cet algorithme tient dans la manière dont sont mis à jour les poids. L'idée est de prendre le vecteur des poids, puis d'ajouter ou soustraire l'historique suivant le résultat du branchement. Les poids évoluent ainsi à chaque branchement, améliorant leurs prédictions d'une exécution à l'autre. La règle de mise à jour des poids est donc la suivante :
: <math>w_i \leftarrow w_i + t \times h_i</math>, avec t = 1 pour un branchement pris, -1 pour un branchement non-pris.
====L'implémentation matérielle des perceptrons et ses optimisations====
Les calculs réalisés par un perceptrons sont simples, juste des multiplications et des additions et cela se sent dans la conception du circuit. Le circuit est composé de plusieurs perceptrons, un par entrée du BTB, un par branchement pour simplifier. Pour cela, on utilise une mémoire SRAM qui mémorise les poids de chaque perceptron. La SRAM est adressée par les bits de poids faible du ''program counter'', de l'adresse du branchement. Une fois les poids récupérés, ils sont envoyés à un circuit de calcul de la prédiction, composé de circuits multiplieurs suivis par un additionneur multi-opérande. Le circuit de mise à jour des poids récupère la prédiction, les poids du perceptron, mais aussi le résultat du branchement. La mise à jour étant une simple addition, le circuit est composé d'additionneurs/soustracteurs, couplés à des registres pour mémoriser la prédiction et les poids du perceptron en attendant que le résultat du branchement soit disponible. Vu qu'il y a un délai de quelques cycles avant que le résultat du branchement soit disponible et que les prédictions doivent continuer pendant ce temps, les poids du perceptron et la prédiction sont stockés dans des FIFOs lues/écrites à chaque cycle.
[[File:Unité de prédiction de branchement basée sur des perceptrons.png|centre|vignette|upright=2|Unité de prédiction de branchement basée sur des perceptrons]]
Rien de bien compliqué sur le principe, mais un tel circuit pose un problème en pratique. Rappelons que le perceptron doit fournir un résultat en moins d'un cycle d'horloge, et cela tient compte du temps nécessaire pour sélectionner le perceptron à partir de l'adresse du branchement. C'est un temps très court, surtout pour un circuit qui implique des multiplications. Or, le temps de calcul d'une addition est déjà très proche d'un cycle d'horloge, alors que les multiplications prennent facilement deux à trois cycles d'horloge. Autant dire que si on ajoute le temps d'accès à une petite SRAM, pour récupérer le perceptron, c'est presque impossible. Mais quelques optimisations simples permettent de rendre le calcul plus rapide.
Premièrement, la taille de la SRAM est réduite grâce à quelques économies assez simples. Notamment, le perceptron utilise des poids codés sur quelques bits, généralement un octet, parfois 7 ou 5 bits, guère plus. Les poids sont encodés en complément à 1 ou en complément à 2, là où les perceptrons normaux utilisent des nombres flottants. Rien que ces deux choix simplifient les circuits et les rendent plus rapides. Le désavantage d'utiliser peu de bits par poids est une perte mineure en termes de taux de prédiction, qui est plus que compensée par l'économie en portes logiques, qui permet d'augmenter la taille de la SRAM. D'autres optimisations portent sur le circuit de calcul. Par exemple, la multiplication <math>h_i \times w_i</math> est facultative. Vu que l'historique contient les valeurs et -1, il suffit d'additionner le poids associé si la valeur est 1 et de le soustraire s'il vaut -1. On peut même simplifier la soustraction et se limiter à une complémentation à un (une inversion des bits du poids). En faisant cela, les circuits multiplieurs disparaissent et le circuit de prédiction se résume à un simple additionneur multi-opérande couplé à quelques inverseurs commandables.
Certains perceptrons prennent en compte à la fois l'historique global et l'historique local pour faire leur prédiction. Les deux historiques sont simplement concaténés et envoyés en entrée du perceptron. Cela marche assez bien car les perceptrons peuvent utiliser des historiques de grande taille sans problèmes. Par contre, il y a besoin d'ajouter une ''branch history table'' au circuit, ce qui demande des portes logiques en plus, mais aussi un temps de calcul supplémentaire (l'accès à cette table n'est pas gratuit).
====Les avantages et inconvénients des perceptrons pour la prédiction de branchements====
L'avantage des unités de prédiction à base de perceptrons est qu'elles utilisent un nombre de portes logiques qui est proportionnel à la taille de l'historique, et non pas exponentiel. Cela permet d'utiliser un historique de grande taille, et donc d'obtenir un taux de bonnes prédictions très élevé, sans avoir à exploser le budget en portes logiques. Leur désavantage est que leurs performances de prédiction sont moins bonnes à historique équivalent. C’est la contrepartie du fait d'utiliser beaucoup moins de portes logiques. Là où les unités de prédictions précédentes avaient des taux de prédictions très corrects mais devaient se débrouiller avec des historiques courts, les perceptrons font l'inverse. Un autre désavantage est que les calculs des perceptrons prennent du temps, et leur prédiction peut facilement prendre deux voire trois cycles d’horloge.
Un autre défaut est que les perceptrons ont des taux de prédiction correctes élevées seulement quand certaines conditions mathématiques sont respectées par les historiques. La condition en question est que les historiques correspondants à un branchement pris et les historiques où ce même branchement est non-pris doivent être linéairement séparables. La notion de linéairement séparable est un peu difficile à comprendre. Pour l'expliquer, imaginez que les N bits de l'historique forme un espace à N-dimensions discrets. Chaque historique est un point dans cet espace N-dimensionnel et chaque bit sert de coordonnée pour se repérer dans cet espace. Une fonction est linéairement séparable si l'espace N-dimensionnel peut être coupé en deux par un hyperplan : d'un côté de ce plan se trouvent les historiques des branchements pris, de l'autre se trouvent les historiques des branchements non-pris. Cet hyperplan est défini par l'ensemble des points qui respecte l'équation suivante :
: <math>w_0 + \sum_i (h_i \times w_i) = 0</math>
Un exemple où cette condition est respectée est quand le résultat d'une prédiction se calcule avec un ET entre plusieurs branchements de l'historique. Si un branchement est pris quand plusieurs branchements de l’historique sont tous pris ou tous non-pris, la condition est respectée et le perceptron donnera des prédictions correctes. Un exemple où cette condition n'est pas respectée est une banale fonction XOR. Prenons l'exemple d'un historique global de 2 bits. Imaginons que le branchement à prédire soit pris : soit quand le premier branchement de l'historique est pris et le second non-pris, soit quand le premier est non-pris et le second pris. Concrètement, la prédiction exacte se calcule en faisant un XOR entre les deux bits de l'historique. Mais dans ce cas, la condition "linéairement séparable" n'est pas respectée et le perceptron n'aura pas des performances optimales. Heureusement, les branchements des programmes informatiques sont une majorité à respecter la condition de séparation linéaire, ce qui rend les perceptrons parfaitement adaptés à la prédiction de branchements.
Les unités de prédictions de branchement neuronales utilisent idéalement un perceptron par branchement. Cela demande d'associer, l'adresse du branchement pour chaque perceptron, généralement en donnant un perceptron par entrée du BTB. Mais une telle organisation n'est pas possible en pratique, car elle demanderait d'ajouter un ''tag'' à chaque perceptron/entrée du BTB, ce qui demanderait beaucoup de circuits et de ressources matérielles. Dans les faits l'association entre un branchement et un perceptron se fait en utilisant les bits de poids faible de l'adresse de branchement. Ces bits de poids faible de l'adresse de branchement sélectionnent le perceptron adéquat, puis celui-ci utilise l'historique global pour faire sa prédiction. Mais faire cela entrainera l'apparition de phénomènes d'''aliasing'', comme pour les unités de prédiction à deux niveaux. Les techniques vues précédemment peuvent en théorie être adaptées sur les unités à perceptrons, dans une certaine mesure.
Notons que les perceptrons sont des algorithmes ou des circuits qui permettent de classer des données d'entrée en deux types. Ici, la donnée d'entrée est l'historique global et la sortie du perceptron indique si le branchement prédit est pris ou non-pris. Ils ne sont donc pas adaptés à d'autres tâches de prédiction, comme pour le préchargement des lignes de cache ou la prédiction de l'adresse de destination d'un branchement, ou toute autre tâche d'exécution spéculative un tant soit peu complexe. Leur utilisation reste cantonnée à la prédiction de branchement proprement dit, guère plus. Par contre, les autres techniques vues plus haut peuvent être utilisées pour le préchargement des lignes de cache, ou d'autres mécanismes de prédiction plus complexes, que nous aborderons dans la suite du cours.
===La prédiction des branchements des boucles===
Les unités de prédiction avec un historique marchent très bien pour prédire les branchements des if...else, mais elles sont inadaptées pour prédire les branchements des boucles. L'usage de la prédiction statique ou de compteurs à saturation permet d'obtenir de meilleures performances pour ce cas de figure. L'idée est d'utiliser deux unités de prédiction séparées : une unité avec un historique, et une autre spécialisée dans les boucles. Concrètement, les unités de prédiction modernes essayent de détecter les branchements des boucles afin de les mettre à part. En soi, ce n'est pas compliqué les branchements des boucles sont presque toujours des branchements descendants et réciproquement. De plus, ces branchements sont pris à chaque répétition de la boucle, et ne sont non-pris que quand on quitte la boucle. Dans ces conditions, la prédiction statique marche très bien pour les boucles, notamment les boucles FOR. De telles unités prédisent que les branchements des boucles sont toujours pris, ce qui donne des prédictions qui sont d'autant meilleures que la boucle est répétée un grand nombre de fois.
Certaines unités de prédiction de branchement sont capables de prédire spécifiquement les branchements de boucles FOR imbriquées, à savoir où une boucle FOR est dans une autre boucle FOR. Concrètement, cela permet de répéter la première boucle FOR plusieurs fois de suite, souvent un grand nombre de fois. Rappelons qu'une boucle FOR répète une série d'instruction N fois, le nombre N étant appelé le '''compteur de boucle'''. Ce qui fait que les branchements d'une boucle FOR sont pris n − 1 fois, la dernière exécution étant non prise. Avec les boucles imbriquées, le compteur de boucle peut changer d'une répétition à l'autre, mais ce n'est que rarement le cas. Dans de nombreux cas, le compteur de boucle reste le même d'une répétition à l'autre. Ce qui fait qu'une fois la boucle exécutée une première fois, on peut prédire à la perfection les exécutions suivantes. Pour cela, il suffit de déterminer le compteur de boucle après la première exécution de la boucle, puis de le mémoriser dans un compteur. Lors des exécutions suivantes de la boucle, on compte le nombre de fois que le branchement de la boucle s'exécute : il est prédit comme pris tant que le compteur est différent du compteur de boucle, mais non-pris en cas d'égalité.
==La prédiction de branchement à deux niveaux==
Les processeurs modernes utilisent une prédiction de branchement à deux niveaux. Une première unité de branchement fournit des résultats rapides, mais peu précis. Elle envoie ses adresses au cache d'instruction, pour charger les instructions prédites, et met à jour le ''program counter'' en conséquence. La seconde est plus lente et fournit des résultats après quelques cycles d'horloge. Ces résultats sont pris en compte lors de l'étage de décodage d'instruction, histoire d'annuler des branchements considérés à tord comme pris/non-pris.
Il y a donc une unité de prédiction rapide, dont le résultat est corrigé par une seconde unité. La seconde unité a le dernier mot, elle corrige ou annule les prédictions de la première. Ce qui lui vaut le nom, en anglais, d'''overiding predictor''. La correction se fait simplement en corrigeant le ''program counter'' et en forçant le décodeur à émettre des NOPs pendant quelques cycles, le temps d'éliminer les instructions chargées à tord.
[[File:Double BTB sur les CPU modernes.png|centre|vignette|upright=2|Double BTB sur les CPU modernes.]]
Un exemple est celui de l'architecture Silvermont. Elle utilisait deux unités de prédiction : un BTB de petite taille avec des compteurs à saturation, et un prédicteur ''gshare'' de grande taille.
Les deux unités de prédiction peuvent utiliser des BTB séparés, de taille différente, au moins deux. Vu que les BTB sont des caches, on les appelle des '''BTB de niveau L0 et L1'''. Par exemple, les processeurs Bulldozer d'AMD disposent de deux BTBs : une BTB L1 de 512 entrées, et une BTB L2 de 5120 entrées. Il faut noter que la BTB pour les branchements indirects vient en plus de ces deux BTBs, elle est souvent utilisée pour compléter la BTB de niveau L1.
Quelques processeurs ont trois niveaux de BTB, avec un niveau L0, L1 et L2. Pour donner quelques exemples, les processeurs Zen d'AMD disposent de plusieurs BTBs, avec des niveaux L0, L1 et L2. Le tableai ci-dessous vous donne lat aille et la latence de chaque niveau de BTB.
{|class="wikitable"
|-
! rowspan="3" | Zen 2
| 16 || 0
|-
| 512 || 1
|-
| 7168 || 4
|-
! rowspan="3" | Zen 3
| 16 || 0
|-
| 1024 || 1
|-
| 6656 + 1536 (branchements indirects)|| 4
|}
==La prédiction avec plusieurs unités de branchement distinctes==
Il est possible de combiner plusieurs unités de prédiction de branchement différentes et de combiner leurs résultats en une seule prédiction. Il est par exemple possible d'utiliser plusieurs unités de prédiction, chacune utilisant des historique de taille différente. Pour donner un exemple simple, on pourrait avoir une unité avec un historique de 2 bits, une autre avec un historique de 3 bits, une autre de 4 bits, etc.
Il est fréquent que les tailles d'historiques utilisées suivent une formule mathématique précise, appelée suite géométrique, ce qui est un mot bien barbare pour dire que l'on passe d'un historique au suivant en multipliant sa taille par une constante bien précise. Par exemple, on pourrait avoir des historiques de taille 2, 4, 8, 16, 32. Avec cette méthode, les branchements qui ont un motif répétitif sont alors parfaitement détectés, peu importe sa taille. Par exemple, un branchement avec un historique dont le cycle est pris, non-pris, non-pris, sera parfaitement prédit avec un historique de 3 bits, 6 bits, ou de 3*n bits, mais pas forcément avec un historique de 4 ou 8 bits. De ce fait, utiliser plusieurs unités de branchement avec plusieurs tailles d'historique fonctionne plutôt bien.
De nombreuses unités de prédiction de branchement combinent les résultats de plusieurs unités de prédiction différentes. Mais elles ne vont pas utiliser un historique par unité, mais une unité d'un certain type (compteurs à saturation, historique global, autre) ou des unités spécialisée dans un type de branchements bien précis. On peut par exemple combiner une unité de prédiction spécialisée dans les boucles, une unité de prédiction 2 bits basée sur des compteurs à saturation, une unité de prédiction statique et une unité ''gshare''. Tout le problème est de décider quelle unité a fait la bonne prédiction, comment combiner les résultats des différentes unités de prédiction. Soit on choisit un résultat qui parait plus fiable que les autres, soit on combine les résultats pour faire une sorte de moyenne des prédictions.
===La méta-prédiction===
La méthode la plus simple de choisir le résultat d'une unité de prédiction parmi toutes les autres. Pour implémenter le tout, il suffit d'un multiplexeur qui effectue le choix de la prédiction. Reste à commander le multiplexeur, ce qui demande un '''circuit méta-prédicteur''' qui sélectionne la bonne unité de prédiction. Le circuit méta-prédicteur est conçu avec les mêmes techniques que les unités de prédiction de branchement elle-mêmes. Dans le cas le plus simple, il s'agit d'un simple compteur à saturation, mis à jour suivant la concordance des prédictions avec le résultat effectif du branchement. On peut aussi utiliser toute autre technique de prédiction vue plus haut, mais cela demande alors plus de circuits. C'est cette technique qui est utilisée dans l'unité de prédiction bimodale vue précédemment.
===La fusion de prédictions===
Une autre solution est de combiner le résultat des différentes unités de prédiction avec une fonction mathématique adéquate. Avec un nombre impair d'unités de prédiction, une méthode assez efficace est de simplement prendre le résultat majoritaire. Si une majorité d'unités pense que le branchement est pris, alors on le considère comme pris, et comme non pris dans le cas contraire. Par contre, avec un nombre pair d'unités, cette méthode simple ne marche pas. Il y a un risque que la moitié des unités de prédiction donne un résultat différent de l'autre moitié. Et faire un choix dans ce cas précis est rarement facile. Une solution serait de prioriser certaines unités, qui auraient un poids plus important dans le calcul de la majorité, de la moyenne, mais elle est rarement appliquée, car elle demande un nombre d'unités important (au moins 4).
L'unité de prédiction de branchement « '''''e-gskew''''' » utilise trois unités gshare et combine leurs résultats avec un vote à majorité. Les trois unités gshare utilisent des XOR légèrement différents, dans le sens où les bits de l'adresse du branchement choisi ne sont pas les mêmes dans les trois unités, sans compter que le résultat du XOR peut subir une modification qui dépend de l'unité de prédiction choisie. Autrement dit, la fonction de hachage qui associe une entrée à un branchement dépend de l'unité.
[[File:Unité de prédiction « e-gskew ».png|centre|vignette|upright=2|Unité de prédiction « e-gskew ».]]
===La priorisation des unités de prédiction===
Une autre technique consiste à prioriser les unités de prédiction de branchement. Une unité de prédiction complexe est utilisé en premier, mais une seconde unité plus simple (généralement des compteurs à saturation) prend le relai en cas de non-prédiction.
[[File:Partial tag matching.png|centre|vignette|upright=2|Partial tag matching.]]
==Les optimisations de la prédiction de branchements==
La prédiction de branchement est complexe et toute économie est bonne à prendre. Pour améliorer les performances, ou simplement améliorer l'utilisation du BTB et des PHT, diverses techniques ont été inventées. Ces techniques marchent quel que soit l'unité de prédiction prise en compte. Elles peuvent s'appliquer aussi bien aux unités basées sur des perceptrons, que sur des unités à deux niveaux ou des unités de prédiction dynamique en général. Ces techniques sont assez variés : certaines profitent du fait que de nombreux branchements sont biaisés, d'autres tentent de réduire les interférences entre branchements sans utiliser l'historique global/local, etc.
===Le filtrage de branchements===
Une première optimisation permet d'économiser l'usage des différentes ressources matérielles, comme la BTB ou les PHT, en les réservant aux branchements non-biaisés. Idéalement, les branchements biaisés peuvent être prédits en utilisant des techniques très simples. D'où l'idée d'utiliser deux unités de prédiction spécialisées : une pour les branchements biaisés et une autre plus complexe. L'unité de prédiction pour les branchements biaisés est une unité de prédiction à 1 bit qui indique si le branchement est pris ou non, ce qui suffit largement pour de tels branchements.
Cette technique s'appelle le '''filtrage de branchement''' et son nom est assez parlant. On filtre les branchements biaisés pour éviter qu'ils utilisent des PHT et d'autres ressources qui gagneraient à être utilisées pour des branchements plus difficiles à prédire. Appliquer cette idée demande de reconnaître les branchements fortement biaisés d'une manière ou d'une autre, ce qui est plus facile à dire qu'à faire. Une solution similaire enregistre quels branchements sont biaisés dans le BTB, et utilise cette information pour faire des prédictions.
===L'usage de fonctions de hashage pour indexer les diverses SRAM/tables===
Plus haut, nous avons vu que les unités de prédiction de branchement contiennent des structures qui sont adressées par l'adresse de branchement. Tel est le cas du ''Branch Target Buffer'', de certaines PHT globales ou locales, de la ''Branch History Table'' qui stocke les historiques locaux, ou encore de la SRAM des poids des unités à base de perceptrons. En pratique, ces structures sont adressées non pas par l'adresse de branchement complète, mais pas les bits de poids faible de cette adresse. Cela a pour conséquence l'apparition d'un ''aliasing'' lié au fait que ces structures vont confondre deux branchements pour lesquels les bits de poids faible de l'adresse sont identiques. Il est possible d'utiliser autre chose que les bits de poids faible de l'adresse du branchement, afin de limiter les interférences. Et les possibilités sont multiples. Les possibilités en question s'inspirent des traitements effectués sur les adresses des banques dans les mémoires évoluées.
Une première solution serait de faire un XOR entre les bits de poids faible de l'adresse du branchement, et d'autres bits de cette même adresse. Ainsi, deux branchements éloignés en mémoire donneraient des résultats différents, même si leurs bits de poids faible sont identiques.
Une autre possibilité serait de diviser l'adresse du branchement par un nombre et de garder le reste. Ce calcul de modulo n'est en soi pas très différent du fait de conserver seulement les bits de poids faible. Conserver les N bits de poids faible consiste en effet à prendre le modulo par 2^N de l'adresse. Ici, l'idée serait de faire un modulo par un nombre P, qui serait un nombre premier proche de 2^N. Le fait que le nombre soit premier limite les cas où deux adresses différentes donneraient le même reste, ce qui réduit l'''aliasing''. Le fait de prendre un nombre proche de 2^N pour une entrée de N bits est que le résultat reste assez proche de ce qu'on obtiendrait en gardant les bits de poids faible, cette dernière étant une solution pas trop mauvaise. D'autres possibilités similaires se basent sur des réductions polynomiales ou d'autres astuces impliquant des nombres premiers.
L'efficacité de ces méthodes dépend grandement de la taille de la SRAM/table considérée, des adresses des branchements et de beaucoup d'autres paramètres. La réduction des interférences par telle ou telle méthode dépend aussi de l'unité de prédiction considérée. Les résultats ne sont pas les mêmes selon que l'on parle d'une unité à perceptrons ou d'une unité à deux niveaux ou d'unités à base de compteurs à saturation, ou que l'on parle du BTB. Par contre, toutes les méthodes ne sont pas équivalentes en termes de temps de calcul ou de portes logiques. Autant la première solution avec un XOR ajoute un temps de calcul négligeable et quelques portes logiques, autant les autres méthodes requièrent l'ajout d'un diviseur très lent et gourmand en portes logiques pour calculer des modulos. Elles ne sont généralement pas praticables pour ces raisons, le temps de calcul serait trop élevé.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le pipeline
| prevText=Le pipeline
| next=Les optimisations du chargement des instructions
| nextText=Les optimisations du chargement des instructions
}}
</noinclude>
o453iexxuln1cf6q82to48fv2s34z3b
Fonctionnement d'un ordinateur/Les processeurs superscalaires
0
65956
773300
773254
2026-09-27T15:57:18Z
Mewtow
31375
/* La micro-fusion des processeurs Intel et AMD */
773300
wikitext
text/x-wiki
Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''.
Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''.
[[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]]
Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur.
==Le nombre de voies d'un processeur superscalaire==
Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme !
Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague.
Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres.
===Les contraintes d’appariement===
Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc.
En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire.
Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps.
===L'impact des contraintes d’appariement sur la performance===
Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout.
La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement.
===Terminologie pour la suite du chapitre===
Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes :
* INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter.
* MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division.
* FLOAT désigne une opération flottante, peut importe laquelle.
* MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire.
* BRANCH désigne une instruction de branchement.
Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière.
J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante.
==Le ''front-end'' des processeurs superscalaires==
Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple.
Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail.
{|class="wikitable"
|-
! rowspan="2" | Processeur sans émission multiple
| rowspan="2" | Chargement
| rowspan="2" | Décodage
| rowspan="2" | Renommage
| rowspan="2" | Émission
| Exécution / ALU
| rowspan="2" | ''Commit''/ROB
|-
| Exécution / ALU
|-
! rowspan="4" | Processeur superscalaire
| rowspan="4" | Chargement
| rowspan="2" | Décodage
| rowspan="4" | Renommage
| rowspan="4" | Émission
| Exécution / ALU
| rowspan="4" | ''Commit''/ROB
|-
| Exécution / ALU
|-
| rowspan="2" | Décodage
| Exécution / ALU
|-
| Exécution / ALU
|}
Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs.
===Le chargement de plusieurs instructions consécutives===
Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large.
Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur.
Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence.
* Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas.
* Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc.
[[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]]
Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable.
Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance.
Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache.
===Le décodage parallèle des instructions===
Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits.
Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur.
Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps.
Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops.
===L'émission parallèle des instructions===
La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations.
Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage).
Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates.
Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''.
==Les processeurs implémentés avec plusieurs pipelines==
Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre.
Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue :
* Les processeurs avec plusieurs pipelines, sans exécution dans le désordre.
* Les processeurs superscalaires avec un pipeline dynamique.
* Les processeurs avec des fenêtres d'instruction décentralisées.
La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''.
La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction.
Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section.
===La double émission entière-flottante===
L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW.
[[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]]
Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste.
[[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]]
Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue.
{|
|-
|[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]]
|[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]]
|}
Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire.
Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine.
Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants.
===La double émission entière===
Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait.
Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86.
[[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]]
L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions.
[[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]]
==Les processeurs superscalaires dynamiques==
Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul.
Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section.
La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités.
===Le partitionnement des unités fonctionnelles===
Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire.
Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques.
Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément.
Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée.
Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu.
Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603.
Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''.
A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur.
Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions).
===La duplication des ALU entières===
Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique.
Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''.
Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus.
[[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]]
La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle.
Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle.
Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas.
{|class="wikitable"
|-
! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3
|-
| ALU entière || ALU entière || FPU
|}
Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission.
L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes.
Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes.
===Le partage des ports d'émission===
La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité.
[[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]]
Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul.
[[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]]
La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière.
Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie.
[[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]]
En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas.
{|class="wikitable"
|-
! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3
|-
|
* ALU entière
* Multiplieur
|
* ALU entière
* Barrel shifter
| FPU
|-
|
* Additions, soustractions, opérations bit à bit, etc.
* Multiplication
|
* Additions, soustractions, opérations bit à bit, etc.
* Décalages et rotations
| Opération flottante
|}
Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés.
La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres.
L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières.
Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement.
Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT).
: Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement.
===La duplication des autres unités fonctionnelles===
Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale.
Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc.
La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement.
Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent.
Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications.
Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme.
===Les CPU superscalaires mélangent les trois techniques===
Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents.
* Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU.
* La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants.
* La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables.
* Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait.
L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires.
L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail.
Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur.
{|class="wikitable"
|-
! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres
|-
! 1989
| RS/6000 || || || i960CA || ||
|-
! 1990
| || || || || ||
|-
! 1991
| || || || || || Motorola 88110
|-
! 1992
| RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 ||
|-
! 1993
| PPC 601 || || HyperSPARC || Pentium || ||
|-
! 1994
| class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 ||
|-
! 1995
| 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000
|-
! 1996
| class="f_bleu | 620 || || || || class="f_bleu | 21264 ||
|-
! ...
|| ... || ... || ... || ... || ... || ...
|}
Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique.
* Les CPU en vert utilisaient la double émission entière-flottante.
* Les CPU en rouge utilisaient la double émission entière "pure".
* Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire.
* Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission.
* Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante.
{|class="wikitable"
|-
! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres
|-
! 1989
| class="f_vert | RS/6000 || || || class="f_rouge | i960CA || ||
|-
! 1990
| || || || || ||
|-
! 1991
| || || || || || Motorola 88110
|-
! 1992
| class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 ||
|-
! 1993
| class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || ||
|-
! 1994
| <s>POWER 2</s> || PA 7200 || || || <s>21164</s> ||
|}
C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires.
La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission.
La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop.
Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission.
Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui.
Le tout est résumé dans le tableau ci-dessous, avec :
* en vert les CPU utilisant la première stratégie ;
* en rouge les CPU utilisant la seconde stratégie ;
* en jaune les CPU utilisant la troisième stratégie.
{|class="wikitable"
|+ Processeurs quadruple émission historiques
|-
! Année !! Processeur !! Unités
|-
! 1991
| class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques
|-
|-
! rowspan="2" | 1994
| class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'')
|-
| class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM
|-
! rowspan="3" | 1995
| PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD
|-
| class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques
|-
| MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM
|-
! rowspan="2" | 1996
| class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM
|-
| class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL)
|}
Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission.
Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières.
Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières.
Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière.
Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !
|-
| ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE
|-
| || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA)
|-
| || ''Barrel shifter'' || || || || Unité pour les instructions PUSH
|}
Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !
|-
| ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations)
|-
| Multiplieur || || || || ||
|-
| LOAD/STORE || LOAD/STORE || LOAD/STORE || || ||
|}
Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair.
Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10
|-
| ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE
|-
| ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || ||
|-
| Branchements || Diviseur || || Branchements || || || || || ||
|}
==Les µops mémoire sur les processeurs superscalaires==
Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une.
* Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières.
* Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''.
* Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''.
Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire.
===Les accès mémoire traités dans le pipeline entier===
Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante.
[[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]]
Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace !
[[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]]
L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2.
Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ.
Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante...
===L'émission parallèle des accès mémoire===
A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière.
La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple.
L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle.
Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles.
===L'émission multiple des accès mémoire avec une LSQ===
Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures.
Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares.
L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse.
: Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''.
[[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]]
Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire.
La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent
La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus.
Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant.
===L'émission séparée des LOAD et STORE===
De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures.
En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture.
Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache.
[[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]]
L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée.
Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''.
Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique.
[[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]]
==Les CPU superscalaire à exécution dans le désordre==
La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops.
Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle.
Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire.
===La superscalarité avec une file/fenêtre d'instruction centralisée===
Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé.
Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions.
Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission.
Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance.
Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction.
Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre.
La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre.
Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage.
===La superscalarité à fenêtres d'instruction décentralisées===
Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées.
Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''.
Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle.
Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000.
[[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]]
L'exemple précédent avait assez d'unités entières/flottantes pour couvrir toute la largeur de décodage/''Dispatch''. Mais ce n'est pas toujours le cas, comme va le montrer l'exemple suivant.
Le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2.
Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6 µops ''schedule'' pour 4 µops au ''dispatch''.
[[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]]
===L'unité de renommage superscalaire===
Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées.
Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat.
[[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]]
Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante.
[[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]]
Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande.
[[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]]
==Les optimisations des processeurs superscalaires larges==
Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul.
===L'étape de chargement d'un CPU superscalaire large===
Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble.
Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part.
La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime.
La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements.
Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées.
[[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]]
Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles.
[[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]]
Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements !
[[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]]
Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''.
===Le décodage superscalaire large===
Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle.
Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total).
Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''.
Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle.
Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''.
Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''.
===Le contournement sur les processeurs superscalaires larges===
Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données.
[[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]]
La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup.
Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant.
La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée.
===Le banc de registre des processeurs superscalaires larges===
Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions.
La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter'').
Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres.
Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''.
[[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]]
Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat.
==La macro-fusion : une optimisation du décodage parallèle==
Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes.
La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP.
Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''.
Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis.
La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs...
==Le ''stack engine'' : une optimisation de la pile d'appel==
Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière.
L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié.
===Le ''stack engine''===
L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle.
Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre.
L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire.
Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''.
Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits.
===Les points de synchronisation du delta===
La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué.
D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte.
Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème.
La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération.
==La micro-fusion et la délamination==
La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications.
L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage.
L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre.
Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés.
===Les domaines de fusion===
La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc.
J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc.
Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté.
Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation.
Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU.
Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type.
===La délamination===
La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement.
Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations.
===La micro-fusion des processeurs Intel et AMD===
La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''.
Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''.
Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout.
La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le parallélisme mémoire
| prevText=Le parallélisme mémoire
| next=Les microarchitectures pour le x86 : haute performance
| nextText=Les microarchitectures pour le x86 : haute performance
}}
</noinclude>
sbk4ee88sz66a1eb2ktmgxcjlfgszzn
773351
773300
2026-09-27T20:38:56Z
Mewtow
31375
/* La superscalarité à fenêtres d'instruction décentralisées */
773351
wikitext
text/x-wiki
Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''.
Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''.
[[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]]
Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur.
==Le nombre de voies d'un processeur superscalaire==
Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme !
Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague.
Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres.
===Les contraintes d’appariement===
Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc.
En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire.
Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps.
===L'impact des contraintes d’appariement sur la performance===
Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout.
La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement.
===Terminologie pour la suite du chapitre===
Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes :
* INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter.
* MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division.
* FLOAT désigne une opération flottante, peut importe laquelle.
* MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire.
* BRANCH désigne une instruction de branchement.
Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière.
J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante.
==Le ''front-end'' des processeurs superscalaires==
Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple.
Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail.
{|class="wikitable"
|-
! rowspan="2" | Processeur sans émission multiple
| rowspan="2" | Chargement
| rowspan="2" | Décodage
| rowspan="2" | Renommage
| rowspan="2" | Émission
| Exécution / ALU
| rowspan="2" | ''Commit''/ROB
|-
| Exécution / ALU
|-
! rowspan="4" | Processeur superscalaire
| rowspan="4" | Chargement
| rowspan="2" | Décodage
| rowspan="4" | Renommage
| rowspan="4" | Émission
| Exécution / ALU
| rowspan="4" | ''Commit''/ROB
|-
| Exécution / ALU
|-
| rowspan="2" | Décodage
| Exécution / ALU
|-
| Exécution / ALU
|}
Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs.
===Le chargement de plusieurs instructions consécutives===
Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large.
Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur.
Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence.
* Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas.
* Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc.
[[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]]
Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable.
Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance.
Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache.
===Le décodage parallèle des instructions===
Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits.
Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur.
Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps.
Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops.
===L'émission parallèle des instructions===
La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations.
Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage).
Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates.
Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''.
==Les processeurs implémentés avec plusieurs pipelines==
Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre.
Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue :
* Les processeurs avec plusieurs pipelines, sans exécution dans le désordre.
* Les processeurs superscalaires avec un pipeline dynamique.
* Les processeurs avec des fenêtres d'instruction décentralisées.
La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''.
La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction.
Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section.
===La double émission entière-flottante===
L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW.
[[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]]
Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste.
[[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]]
Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue.
{|
|-
|[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]]
|[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]]
|}
Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire.
Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine.
Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants.
===La double émission entière===
Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait.
Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86.
[[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]]
L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions.
[[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]]
==Les processeurs superscalaires dynamiques==
Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul.
Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section.
La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités.
===Le partitionnement des unités fonctionnelles===
Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire.
Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques.
Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément.
Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée.
Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu.
Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603.
Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''.
A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur.
Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions).
===La duplication des ALU entières===
Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique.
Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''.
Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus.
[[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]]
La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle.
Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle.
Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas.
{|class="wikitable"
|-
! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3
|-
| ALU entière || ALU entière || FPU
|}
Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission.
L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes.
Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes.
===Le partage des ports d'émission===
La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité.
[[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]]
Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul.
[[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]]
La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière.
Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie.
[[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]]
En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas.
{|class="wikitable"
|-
! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3
|-
|
* ALU entière
* Multiplieur
|
* ALU entière
* Barrel shifter
| FPU
|-
|
* Additions, soustractions, opérations bit à bit, etc.
* Multiplication
|
* Additions, soustractions, opérations bit à bit, etc.
* Décalages et rotations
| Opération flottante
|}
Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés.
La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres.
L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières.
Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement.
Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT).
: Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement.
===La duplication des autres unités fonctionnelles===
Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale.
Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc.
La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement.
Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent.
Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications.
Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme.
===Les CPU superscalaires mélangent les trois techniques===
Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents.
* Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU.
* La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants.
* La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables.
* Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait.
L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires.
L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail.
Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur.
{|class="wikitable"
|-
! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres
|-
! 1989
| RS/6000 || || || i960CA || ||
|-
! 1990
| || || || || ||
|-
! 1991
| || || || || || Motorola 88110
|-
! 1992
| RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 ||
|-
! 1993
| PPC 601 || || HyperSPARC || Pentium || ||
|-
! 1994
| class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 ||
|-
! 1995
| 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000
|-
! 1996
| class="f_bleu | 620 || || || || class="f_bleu | 21264 ||
|-
! ...
|| ... || ... || ... || ... || ... || ...
|}
Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique.
* Les CPU en vert utilisaient la double émission entière-flottante.
* Les CPU en rouge utilisaient la double émission entière "pure".
* Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire.
* Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission.
* Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante.
{|class="wikitable"
|-
! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres
|-
! 1989
| class="f_vert | RS/6000 || || || class="f_rouge | i960CA || ||
|-
! 1990
| || || || || ||
|-
! 1991
| || || || || || Motorola 88110
|-
! 1992
| class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 ||
|-
! 1993
| class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || ||
|-
! 1994
| <s>POWER 2</s> || PA 7200 || || || <s>21164</s> ||
|}
C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires.
La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission.
La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop.
Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission.
Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui.
Le tout est résumé dans le tableau ci-dessous, avec :
* en vert les CPU utilisant la première stratégie ;
* en rouge les CPU utilisant la seconde stratégie ;
* en jaune les CPU utilisant la troisième stratégie.
{|class="wikitable"
|+ Processeurs quadruple émission historiques
|-
! Année !! Processeur !! Unités
|-
! 1991
| class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques
|-
|-
! rowspan="2" | 1994
| class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'')
|-
| class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM
|-
! rowspan="3" | 1995
| PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD
|-
| class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques
|-
| MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM
|-
! rowspan="2" | 1996
| class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM
|-
| class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL)
|}
Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission.
Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières.
Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières.
Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière.
Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !
|-
| ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE
|-
| || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA)
|-
| || ''Barrel shifter'' || || || || Unité pour les instructions PUSH
|}
Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !
|-
| ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations)
|-
| Multiplieur || || || || ||
|-
| LOAD/STORE || LOAD/STORE || LOAD/STORE || || ||
|}
Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair.
Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10
|-
| ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE
|-
| ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || ||
|-
| Branchements || Diviseur || || Branchements || || || || || ||
|}
==Les µops mémoire sur les processeurs superscalaires==
Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une.
* Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières.
* Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''.
* Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''.
Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire.
===Les accès mémoire traités dans le pipeline entier===
Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante.
[[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]]
Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace !
[[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]]
L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2.
Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ.
Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante...
===L'émission parallèle des accès mémoire===
A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière.
La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple.
L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle.
Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles.
===L'émission multiple des accès mémoire avec une LSQ===
Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures.
Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares.
L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse.
: Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''.
[[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]]
Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire.
La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent
La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus.
Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant.
===L'émission séparée des LOAD et STORE===
De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures.
En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture.
Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache.
[[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]]
L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée.
Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''.
Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique.
[[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]]
==Les CPU superscalaire à exécution dans le désordre==
La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops.
Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle.
Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire.
===La superscalarité avec une file/fenêtre d'instruction centralisée===
Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé.
Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions.
Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission.
Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance.
Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction.
Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre.
La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre.
Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage.
===La superscalarité à fenêtres d'instruction décentralisées===
Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées.
Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''.
Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle.
Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000.
[[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]]
L'exemple précédent avait assez d'unités entières/flottantes pour couvrir toute la largeur de décodage/''Dispatch''. Mais ce n'est pas toujours le cas, comme va le montrer l'exemple suivant.
Le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2.
Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6 µops ''schedule'' pour 4 µops au ''dispatch''.
[[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]]
===La superscalarité à fenêtres d'instruction totalement décentralisées===
Prenons un cas extrême de fenêtre d'instruction décentralisée : celui où chaque ALU entière a sa propre station de réservation. En clair : une fenêtre d'instruction par ALU/FPU ! On parle de '''fenêtres d'instruction totalement décentralisées'''. Vous vous dites que ce cas est un peu trop extrême pour être réaliste, mais il n'en est rien. C'est même très courant sur les architectures basse consommation, et sur pas mal de CPU x86 modernes.
Une telle organisation entraine une légère perte de performance, comparé à une fenêtre d'instruction centralisée ou partiellement décentralisée. Et cela peut se comprendre par un exemple. Imaginez un processeur avec deux ALU, donc deux stations de réservation. Il arrivera qu'une station de réservation ait plusieurs µops prêtes, alors que l'autre en ait zéro. Dans une telle situation, une seule µop sera émise au cycle suivant, vu que chaque station de réservation a un seul port d'émission vers une seule ALU entière. Par contre, avec une fenêtre d'instruction centralisée double port, on aurait pu émettre les deux µops en même temps sur deux ALUs séparées.
La perte de performance est donc liée au fait que les µops '''''prêtes''''' sont mal réparties dans les différentes stations de réservation. Il arrivera que des µops prêtes s'accumulent dans une station de réservation, alors que d'autres seront pleines, ou du moins sous-utilisées. Et ce n'est pas quelque chose qui peut être réglé à l'étape de ''dispatch'', car on ne sait pas encore quand les µops auront leurs opérandes de disponibles.
Par contre, décentraliser totalement les fen^tres d'instruction entraine aussi un gain considérable en termes de consommation énergétique. Et c'est la raison pour laquelle les fenêtres d'instructions totalement décentralisées sont utilisées sur les architectures basse consommation. La perte de performance ne peut pas s'expliquer clairement à ce stade du cours, car il nous faudrait parler d'émission superscalaire. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). Prenons une station de réservation centralisée de 64 entrées et comparons avec 8 stations de 8 entrées chacune. Les deux donneront 64 entrées, mais la première aura un cout en circuit proportionnel à 64², alors que le second aura un cout en circuit de 8 *(8²). Entre les deux, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution sur les microarchitectures basse consommation.
===L'unité de renommage superscalaire===
Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées.
Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat.
[[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]]
Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante.
[[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]]
Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande.
[[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]]
==Les optimisations des processeurs superscalaires larges==
Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul.
===L'étape de chargement d'un CPU superscalaire large===
Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble.
Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part.
La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime.
La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements.
Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées.
[[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]]
Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles.
[[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]]
Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements !
[[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]]
Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''.
===Le décodage superscalaire large===
Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle.
Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total).
Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''.
Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle.
Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''.
Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''.
===Le contournement sur les processeurs superscalaires larges===
Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données.
[[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]]
La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup.
Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant.
La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée.
===Le banc de registre des processeurs superscalaires larges===
Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions.
La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter'').
Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres.
Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''.
[[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]]
Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat.
==La macro-fusion : une optimisation du décodage parallèle==
Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes.
La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP.
Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''.
Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis.
La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs...
==Le ''stack engine'' : une optimisation de la pile d'appel==
Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière.
L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié.
===Le ''stack engine''===
L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle.
Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre.
L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire.
Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''.
Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits.
===Les points de synchronisation du delta===
La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué.
D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte.
Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème.
La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération.
==La micro-fusion et la délamination==
La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications.
L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage.
L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre.
Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés.
===Les domaines de fusion===
La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc.
J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc.
Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté.
Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation.
Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU.
Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type.
===La délamination===
La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement.
Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations.
===La micro-fusion des processeurs Intel et AMD===
La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''.
Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''.
Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout.
La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le parallélisme mémoire
| prevText=Le parallélisme mémoire
| next=Les microarchitectures pour le x86 : haute performance
| nextText=Les microarchitectures pour le x86 : haute performance
}}
</noinclude>
qsptlhzsl7pn46c0tgo8z5xww6thsw1
773353
773351
2026-09-27T20:47:51Z
Mewtow
31375
/* La superscalarité à fenêtres d'instruction totalement décentralisées */
773353
wikitext
text/x-wiki
Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''.
Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''.
[[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]]
Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur.
==Le nombre de voies d'un processeur superscalaire==
Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme !
Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague.
Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres.
===Les contraintes d’appariement===
Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc.
En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire.
Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps.
===L'impact des contraintes d’appariement sur la performance===
Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout.
La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement.
===Terminologie pour la suite du chapitre===
Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes :
* INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter.
* MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division.
* FLOAT désigne une opération flottante, peut importe laquelle.
* MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire.
* BRANCH désigne une instruction de branchement.
Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière.
J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante.
==Le ''front-end'' des processeurs superscalaires==
Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple.
Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail.
{|class="wikitable"
|-
! rowspan="2" | Processeur sans émission multiple
| rowspan="2" | Chargement
| rowspan="2" | Décodage
| rowspan="2" | Renommage
| rowspan="2" | Émission
| Exécution / ALU
| rowspan="2" | ''Commit''/ROB
|-
| Exécution / ALU
|-
! rowspan="4" | Processeur superscalaire
| rowspan="4" | Chargement
| rowspan="2" | Décodage
| rowspan="4" | Renommage
| rowspan="4" | Émission
| Exécution / ALU
| rowspan="4" | ''Commit''/ROB
|-
| Exécution / ALU
|-
| rowspan="2" | Décodage
| Exécution / ALU
|-
| Exécution / ALU
|}
Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs.
===Le chargement de plusieurs instructions consécutives===
Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large.
Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur.
Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence.
* Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas.
* Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc.
[[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]]
Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable.
Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance.
Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache.
===Le décodage parallèle des instructions===
Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits.
Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur.
Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps.
Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops.
===L'émission parallèle des instructions===
La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations.
Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage).
Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates.
Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''.
==Les processeurs implémentés avec plusieurs pipelines==
Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre.
Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue :
* Les processeurs avec plusieurs pipelines, sans exécution dans le désordre.
* Les processeurs superscalaires avec un pipeline dynamique.
* Les processeurs avec des fenêtres d'instruction décentralisées.
La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''.
La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction.
Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section.
===La double émission entière-flottante===
L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW.
[[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]]
Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste.
[[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]]
Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue.
{|
|-
|[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]]
|[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]]
|}
Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire.
Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine.
Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants.
===La double émission entière===
Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait.
Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86.
[[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]]
L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions.
[[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]]
==Les processeurs superscalaires dynamiques==
Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul.
Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section.
La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités.
===Le partitionnement des unités fonctionnelles===
Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire.
Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques.
Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément.
Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée.
Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu.
Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603.
Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''.
A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur.
Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions).
===La duplication des ALU entières===
Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique.
Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''.
Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus.
[[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]]
La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle.
Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle.
Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas.
{|class="wikitable"
|-
! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3
|-
| ALU entière || ALU entière || FPU
|}
Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission.
L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes.
Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes.
===Le partage des ports d'émission===
La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité.
[[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]]
Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul.
[[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]]
La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière.
Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie.
[[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]]
En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas.
{|class="wikitable"
|-
! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3
|-
|
* ALU entière
* Multiplieur
|
* ALU entière
* Barrel shifter
| FPU
|-
|
* Additions, soustractions, opérations bit à bit, etc.
* Multiplication
|
* Additions, soustractions, opérations bit à bit, etc.
* Décalages et rotations
| Opération flottante
|}
Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés.
La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres.
L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières.
Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement.
Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT).
: Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement.
===La duplication des autres unités fonctionnelles===
Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale.
Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc.
La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement.
Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent.
Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications.
Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme.
===Les CPU superscalaires mélangent les trois techniques===
Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents.
* Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU.
* La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants.
* La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables.
* Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait.
L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires.
L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail.
Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur.
{|class="wikitable"
|-
! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres
|-
! 1989
| RS/6000 || || || i960CA || ||
|-
! 1990
| || || || || ||
|-
! 1991
| || || || || || Motorola 88110
|-
! 1992
| RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 ||
|-
! 1993
| PPC 601 || || HyperSPARC || Pentium || ||
|-
! 1994
| class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 ||
|-
! 1995
| 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000
|-
! 1996
| class="f_bleu | 620 || || || || class="f_bleu | 21264 ||
|-
! ...
|| ... || ... || ... || ... || ... || ...
|}
Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique.
* Les CPU en vert utilisaient la double émission entière-flottante.
* Les CPU en rouge utilisaient la double émission entière "pure".
* Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire.
* Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission.
* Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante.
{|class="wikitable"
|-
! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres
|-
! 1989
| class="f_vert | RS/6000 || || || class="f_rouge | i960CA || ||
|-
! 1990
| || || || || ||
|-
! 1991
| || || || || || Motorola 88110
|-
! 1992
| class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 ||
|-
! 1993
| class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || ||
|-
! 1994
| <s>POWER 2</s> || PA 7200 || || || <s>21164</s> ||
|}
C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires.
La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission.
La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop.
Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission.
Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui.
Le tout est résumé dans le tableau ci-dessous, avec :
* en vert les CPU utilisant la première stratégie ;
* en rouge les CPU utilisant la seconde stratégie ;
* en jaune les CPU utilisant la troisième stratégie.
{|class="wikitable"
|+ Processeurs quadruple émission historiques
|-
! Année !! Processeur !! Unités
|-
! 1991
| class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques
|-
|-
! rowspan="2" | 1994
| class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'')
|-
| class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM
|-
! rowspan="3" | 1995
| PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD
|-
| class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques
|-
| MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM
|-
! rowspan="2" | 1996
| class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM
|-
| class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL)
|}
Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission.
Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières.
Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières.
Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière.
Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !
|-
| ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE
|-
| || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA)
|-
| || ''Barrel shifter'' || || || || Unité pour les instructions PUSH
|}
Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !
|-
| ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations)
|-
| Multiplieur || || || || ||
|-
| LOAD/STORE || LOAD/STORE || LOAD/STORE || || ||
|}
Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair.
Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies.
{|class="wikitable"
|-
! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10
|-
| ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE
|-
| ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || ||
|-
| Branchements || Diviseur || || Branchements || || || || || ||
|}
==Les µops mémoire sur les processeurs superscalaires==
Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une.
* Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières.
* Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''.
* Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''.
Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire.
===Les accès mémoire traités dans le pipeline entier===
Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante.
[[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]]
Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace !
[[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]]
L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2.
Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ.
Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante...
===L'émission parallèle des accès mémoire===
A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière.
La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple.
L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle.
Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles.
===L'émission multiple des accès mémoire avec une LSQ===
Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures.
Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares.
L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse.
: Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''.
[[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]]
Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire.
La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent
La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus.
Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant.
===L'émission séparée des LOAD et STORE===
De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures.
En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture.
Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache.
[[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]]
L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée.
Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''.
Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique.
[[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]]
==Les CPU superscalaire à exécution dans le désordre==
La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops.
Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle.
Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire.
===La superscalarité avec une file/fenêtre d'instruction centralisée===
Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé.
Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions.
Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission.
Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance.
Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction.
Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre.
La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre.
Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage.
===La superscalarité à fenêtres d'instruction décentralisées===
Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées.
Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''.
Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle.
Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000.
[[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]]
L'exemple précédent avait assez d'unités entières/flottantes pour couvrir toute la largeur de décodage/''Dispatch''. Mais ce n'est pas toujours le cas, comme va le montrer l'exemple suivant.
Le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2.
Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6 µops ''schedule'' pour 4 µops au ''dispatch''.
[[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]]
===La superscalarité à fenêtres d'instruction totalement décentralisées===
Prenons un cas extrême de fenêtre d'instruction décentralisée : celui où chaque ALU entière a sa propre station de réservation. En clair : une fenêtre d'instruction par ALU/FPU ! On parle de '''fenêtres d'instruction totalement décentralisées'''. Vous vous dites que ce cas est un peu trop extrême pour être réaliste, mais il n'en est rien. C'est même très courant sur les architectures basse consommation, et sur pas mal de CPU x86 modernes.
Une telle organisation entraine une légère perte de performance, comparé à une fenêtre d'instruction centralisée ou partiellement décentralisée. Et cela peut se comprendre par un exemple. Imaginez un processeur avec deux ALU, donc deux stations de réservation. Il arrivera qu'une station de réservation ait plusieurs µops prêtes, alors que l'autre en ait zéro. Dans une telle situation, une seule µop sera émise au cycle suivant, vu que chaque station de réservation a un seul port d'émission vers une seule ALU entière. Par contre, avec une fenêtre d'instruction centralisée double port, on aurait pu émettre les deux µops en même temps sur deux ALUs séparées.
La perte de performance est donc liée au fait que les µops '''''prêtes''''' sont mal réparties dans les différentes stations de réservation. Il arrivera que des µops prêtes s'accumulent dans une station de réservation, alors que d'autres seront pleines, ou du moins sous-utilisées. Et ce n'est pas quelque chose qui peut être réglé à l'étape de ''dispatch'', car on ne sait pas encore quand les µops auront leurs opérandes de disponibles.
[[File:Comparaison entre une station de réservation totalement décentralisée et une autre qui ne l'est pas.png|centre|vignette|upright=2.5|Comparaison entre une station de réservation totalement décentralisée et une autre qui ne l'est pas.]]
Par contre, décentraliser totalement les fenêtres d'instruction entraine aussi un gain considérable en termes de consommation énergétique. Et c'est la raison pour laquelle les fenêtres d'instructions totalement décentralisées sont utilisées sur les architectures basse consommation. La perte de performance ne peut pas s'expliquer clairement à ce stade du cours, car il nous faudrait parler d'émission superscalaire. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). Prenons une station de réservation centralisée de 64 entrées et comparons avec 8 stations de 8 entrées chacune. Les deux donneront 64 entrées, mais la première aura un cout en circuit proportionnel à 64², alors que le second aura un cout en circuit de 8 *(8²). Entre les deux, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution sur les microarchitectures basse consommation.
===L'unité de renommage superscalaire===
Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées.
Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat.
[[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]]
Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante.
[[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]]
Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande.
[[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]]
==Les optimisations des processeurs superscalaires larges==
Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul.
===L'étape de chargement d'un CPU superscalaire large===
Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble.
Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part.
La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime.
La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements.
Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées.
[[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]]
Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles.
[[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]]
Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements !
[[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]]
Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''.
===Le décodage superscalaire large===
Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle.
Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total).
Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''.
Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle.
Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''.
Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''.
===Le contournement sur les processeurs superscalaires larges===
Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données.
[[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]]
La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup.
Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant.
La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée.
===Le banc de registre des processeurs superscalaires larges===
Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions.
La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter'').
Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres.
Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''.
[[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]]
Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat.
==La macro-fusion : une optimisation du décodage parallèle==
Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes.
La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP.
Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''.
Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis.
La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs...
==Le ''stack engine'' : une optimisation de la pile d'appel==
Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière.
L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié.
===Le ''stack engine''===
L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle.
Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre.
L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire.
Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''.
Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits.
===Les points de synchronisation du delta===
La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué.
D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte.
Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème.
La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération.
==La micro-fusion et la délamination==
La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications.
L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage.
L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre.
Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés.
===Les domaines de fusion===
La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc.
J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc.
Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté.
Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation.
Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU.
Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type.
===La délamination===
La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement.
Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations.
===La micro-fusion des processeurs Intel et AMD===
La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''.
Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''.
Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout.
La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le parallélisme mémoire
| prevText=Le parallélisme mémoire
| next=Les microarchitectures pour le x86 : haute performance
| nextText=Les microarchitectures pour le x86 : haute performance
}}
</noinclude>
lh9m5ntr2omuiwo500cmhttq0szhbzw
Fonctionnement d'un ordinateur/Les mémoires cache
0
65957
773420
772485
2026-09-28T01:47:42Z
Mewtow
31375
/* L'alignement des lignes de cache */
773420
wikitext
text/x-wiki
Le cache est une mémoire intercalée entre la mémoire et un processeur, plus rarement à l'intérieur d'un périphérique. Il est souvent fabriquée avec de la mémoire SRAM, parfois avec de l'eDRAM. Sans lui, on se croirait à l'âge de pierre tellement nos PC seraient lents ! En effet, la mémoire est très lente comparée au processeur. Le temps mis pour accéder à la mémoire est du temps durant lequel le processeur n'exécute pas d'instruction (sauf cas particuliers impliquant un pipeline). Pour diminuer ce temps d'attente, il a été décidé d'intercaler une mémoire petite mais rapide, entre le processeur et la mémoire. Ainsi, le processeur accède à un cache très rapide plutôt qu'à une RAM beaucoup plus lente.
==L'accès au cache==
Le cache contient une copie de certaines données présentes en RAM. La copie présente dans le cache est accessible bien plus rapidement que celle en RAM, vu que le cache est plus rapide. Mais seule une petite partie de ces données sont copiées dans le cache, les autres données devant être lues ou écrites dans la RAM. Toujours est-il que le cache contient une copie des dernières données accédées par le processeur.
Une donnée est copiée dans la mémoire cache quand elle est lue ou écrite par le processeur. Le processeur conserve une copie de la donnée dans le cache après son premier accès. Les lectures/écritures suivantes se feront alors directement dans le cache. Évidemment, au fur et à mesure des accès, certaines données anciennes sont éliminées du cache pour faire de la place aux nouveaux entrants, comme nous le verrons plus tard.
[[File:Principe d'une mémoire cache.gif|centre|vignette|upright=2|Principe d'une mémoire cache.]]
La mémoire cache est invisible pour le programmeur, qui ne peut pas déceler celles-ci dans l'assembleur. Les accès mémoire se font de la même manière avec ou sans le cache. La raison à cela est que le cache intercepte les accès mémoire et y répond s'il en a la capacité. Par exemple, si le cache intercepte une lecture à une adresse et que le contenu de cette adresse est dans le cache, le cache va outrepasser la mémoire RAM et la donnée sera envoyée par le cache au lieu d'être lue en RAM. par contre, si un accès se fait à une adresse pour laquelle le cache n'a pas la donnée, alors l'accès mémoire sera effectué par la RAM de la même manière que si le cache n'était pas là.
[[File:Accès au cache.png|centre|vignette|upright=2|Accès au cache]]
===Les succès et défauts de caches===
Tout accès mémoire est intercepté par le cache, qui vérifie si la donnée demandée est présente ou non dans le cache. Si la donnée voulue est présente dans le cache, on a un '''succès de cache''' (''cache hit'') et on accède à la donnée depuis le cache. Sinon, c'est un '''défaut de cache''' (''cache miss'') et on est obligé d’accéder à la RAM.
Les défauts de cache peuvent avoir plusieurs origines. Tout ce qu'il faut savoir est que lorsque le processeur accède à une donnée ou une instruction pour la première fois, il la place dans la mémoire cache car elle a de bonnes chances d'être réutilisée prochainement. La raison à cela est qu'un programme a tendance à réutiliser les instructions et données qui ont été accédées dans le passé : c'est le ''principe de localité temporelle''. Bien évidement, cela dépend du programme, de la façon dont celui-ci est programmé et accède à ses données et du traitement qu'il fait, mais c'est souvent vrai en général.
La première cause des défauts de cache est liée à la taille du cache. À force de charger des données/instructions dans le cache, le cache fini par être trop petit pour conserver les anciennes données. Le cache doit bien finir par faire de la place en supprimant les anciennes données, qui ont peu de chances d'être réutilisées. Ces anciennes données éliminées du cache peuvent cependant être accédées plus tard. Tout prochain accès à cette donnée mènera à un cache miss. C'est ce qu'on appelle un ''Capacity Cache Miss'', ou encore '''défaut de capacité'''. Les seules solutions pour éviter cela consistent à augmenter la taille du cache ou à optimiser le programme exécuté (voir plus bas).
Une autre raison pour un défaut est donc la suivante. Lorsqu'on exécute à une instruction ou qu'on accède à donnée pour la première fois, celle-ci n'a pas encore été chargée dans le cache. Le défaut de cache est inévitable : ce genre de cache miss s'appelle un ''Cold Miss'', ou encore un '''défaut à froid'''. De tels défauts sont presque impossibles à éliminer, sauf à utiliser des techniques de préchargement qui chargent à l'avance des données potentiellement utiles. Ces méthodes de préchargement se basent sur le principe de localité spatiale, à savoir le fait que les programmes ont tendance à accéder à des données proches en mémoire. Pour donner un exemple, les instructions d'un programme sont placées en mémoire dans l’ordre dans lequel on les exécute : la prochaine instruction à exécuter est souvent placée juste après l'instruction en cours (sauf avec les branchements). Quand on accède à une donnée ou une instruction, le cache peut précharger les données adjacentes pour en profiter. Nous parlerons de ces techniques de préchargement dans un chapitre dédié, vers la fin du cours.
===Le fonctionnement du cache, vu du processeur===
Vu du processeur, le cache prend en entrée toutes les informations nécessaires pour effectuer un accès mémoire : des signaux de commande, une adresse et la donnée à écrire si besoin. Tout cela est passé en entrée du cache, celui-ci répondant aux accès mémoire via divers bits de contrôles, que le processeur peut lire à souhait. Le cache fournit aussi la donnée à lire, pour les lectures, sur une sortie, connectée directement au bus mémoire/processeur. Globalement, le cache a une capacité limitée, mais il prend en entrée des adresses complètes. Par exemple, sur un processeur 64 bits, le cache prend en entrée des adresses de 64 bits (sauf si optimisations), même si le cache en question ne fait que quelques mébioctets.
Les caches sont souvent des mémoires multiports, surtout sur les processeurs récents. Les caches simple port sont rares, mêmes s'ils existent et ont existé par le passé. les caches double port sont eux plus fréquents, et ont généralement un port d'écriture séparé du port de lecture. Mais les caches récents ont plusieurs ports de lecture/écriture et sont capables de gérer plusieurs accès mémoire simultanés.
Les données présentes dans le cache sont (pré)chargées depuis la mémoire, ce qui fait que toute donnée dans le cache est la copie d'une donnée en mémoire RAM. Le cache doit faire la correspondance entre une donnée du cache et l'adresse mémoire correspondante. Du point de vue du fonctionnement, on peut voir le cache comme une sorte de table de correspondance, qui mémorise des données, chacune étant associée à son adresse mémoire. Le cache contient donc des paires adresse-ligne de cache qui lui permettent de faire le lien entre ligne de cache et adresse. Cela vaut du point de vue du processeur, le fonctionnement interne du cache étant quelque peu différent selon le cache. Il existe des caches dont le fonctionnement interne est bien celui d'une table de correspondance matérielle, d'autres qui sont beaucoup plus optimisés.
[[File:Fonctionnement d'une mémoire associative à correspondance.png|centre|vignette|upright=2|Fonctionnement simplifié d'une mémoire cache : les adresses sont dans la colonne de gauche, les données sont dans la colonne de droite. On voit qu'on envoie l'adresse au cache, que celui-ci répond en renvoyant la donnée associée.]]
==La performance des mémoires caches==
L'analyse de la performance des mémoires caches est plus riche pour celle des autres mémoires. Sa performance dépend de beaucoup de paramètres, mais on peut cependant citer les principaux. Les deux premiers sont tout bonnement sa latence et son débit, comme pour n'importe quelle autre mémoire. La latence est plus importante que son débit, car le processeur est généralement plus rapide que le cache et qu'il n'aime pas attendre. Mais le critère le plus important pour un cache est sa capacité à empêcher des accès mémoire, son efficacité. Plus les accès mémoire sont servis par le cache au lieu de la RAM, meilleures seront les performances. Pour résumer, la performance d'un cache est surtout caractérisée par deux métriques : le taux de défaut, qui correspond à l’efficacité du cache, et la latence du cache.
===Le taux de succès/défaut===
Le '''taux de succès''' (hit ratio) est un premier indicateur des performances du cache, mais un indicateur assez imparfait. C'est le pourcentage d'accès mémoire qui ne déclenchent pas de défaut de cache. Plus il est élevé, plus le processeur accède au cache à la place de la RAM et plus le cache est efficace. Certains chercheurs préfèrent utiliser le '''taux de défauts''', à savoir le pourcentage d'accès mémoire qui entraînent un défaut de cache. Plus il est bas, meilleures sont les performances. Le taux de défaut est relié au taux de succès par l'équation <math>T_\text{succes} = 1 - T_\text{defaut}</math>. Par définition, il est égal à :
: <math>\text{Taux de défauts de cache} = \frac{\text{Nombre de défauts de cache}}{\text{Nombre d’accès mémoires}}</math>
Plutôt que de comparer le nombre de défauts/succès de cache au nombre d'accès mémoire, il est aussi possible de diviser le nombre de défauts par le nombre total d'instructions. On obtient alors le '''taux de défauts/succès par instruction''', une autre métrique utile. Par définition, elle est égale à :
: <math>\text{Taux de défauts par instruction} = \frac{\text{Nombre de défauts de cache}}{\text{Nombre d'instructions}} = \text{Taux de défauts de cache} \times \frac{\text{Nombre d’accès mémoires}}{\text{Nombre d'instructions}}</math>
Si certains défauts de cache sont inévitables quel que soit le cache, comme les défauts à froids, mentionnés plus haut, d'autres défauts peuvent être évités en augmentant la capacité du cache. C'est le cas des défauts de capacité qui sont causés par un accès à une donnée qui a été éliminée du cache faute de place. Plus le cache est gros, moins il a de chances d'être rempli, moins il doit rapatrier de données, plus son taux de succès augmente. Mais nous reviendrons sur le lien entre taille du cache et taux de défaut plus bas.
Le taux de succès ne dépend pas que du cache, mais aussi de la conception des programmes exécutés. Une bonne utilisation du cache (ainsi que de la mémoire virtuelle) repose sur le programmeur qui doit prendre en compte les principes de localités dès la conception de ses programmes.
Par exemple, un programmeur peut parfaitement tenir compte du cache au niveau de son algorithme : on peut citer l'existence des algorithmes ''cache oblivious'', qui sont conçus pour être optimaux quelle que soit la taille du cache. Le programmeur peut aussi choisir ses structures de données de manière à améliorer la localité. Par exemple, un tableau est une structure de donnée respectant le principe de localité spatiale, tandis qu'une liste chaînée ou un arbre n'en sont pas (bien qu'on puisse les implémenter de façon à limiter la casse). D'autres optimisations sont parfois possibles : par exemple, le sens de parcours d'un tableau multidimensionnel peut faire une grosse différence. Cela permet des gains très intéressants pouvant se mesurer avec des nombres à deux ou trois chiffres.
Je vous recommande, si vous êtes programmeur, de vous renseigner le plus possible sur les optimisations de code ou algorithmiques qui concernent le cache : il vous suffira de chercher sur Google. Il y a une citation qui résume bien cela, prononcée par un certain Terje Mathisen. Si vous ne le connaissez pas, cet homme est un vieux programmeur (du temps durant lequel on codait encore en assembleur), grand gourou de l’optimisation, qui a notamment travaillé sur le moteur de Quake 3 Arena.
{{BlocCitation|Almost all programming can be viewed as an exercise in caching.|auteur=Terje Mathisen}}
===La latence moyenne d'un cache===
Le temps mis pour lire ou écrire une donnée varie en présence d'un cache. Certaines lectures/écritures vont atterrir directement dans le cache (succès) tandis que d'autres devront aller chercher leur contenu en mémoire RAM (défaut de cache). Dans tous les cas, qu'il y ait défaut ou non, le cache sera consulté et mettra un certain temps à répondre, égal au temps de latence du cache. Tous les accès mémoires auront donc une durée au moins égale au temps de latence du cache, qui sera notée <math>T_c</math>.
En cas de succès, le cache aura effectué la lecture ou l'écriture, et aucune action supplémentaire n'est requise. Ce qui n'est pas le cas en cas de défaut : le processeur devra aller lire/écrire la donnée en RAM, ce qui prend un temps supplémentaire égal au temps de latence de la mémoire RAM. Un défaut ajoute donc un temps, une pénalité, à l'accès mémoire. Dans ce qui suivra, le temps d'accès à la RAM sera noté <math>T_m</math>. Fort de ces informations, nous pouvons calculer le temps de latence moyen d'un accès mémoire, qui est la somme du temps d'accès au cache (pour tous les accès mémoire), multiplié par le temps lié aux défauts. On a alors :
: <math>T = T_c + \text{Taux de défaut} \times T_m</math>
On voit que plus le taux de succès est élevé, plus le temps de latence moyen sera bas, et inversement. Ce qui explique l'influence du taux de succès sur les performances du cache, influence assez importante sur les processeurs actuels. De nos jours, le temps que passe le processeur dans les défauts de cache devient de plus en plus un problème au fil du temps, et gérer correctement le cache est une nécessité, particulièrement sur les processeurs multi-cœurs.
Il faut dire que la différence de vitesse entre processeur et mémoire est tellement importante que les défauts de cache sont très lents : alors qu'un succès de cache va prendre entre 1 et 5 cycles d'horloge, un cache miss fera plus dans les 400-1000 cycles d'horloge. Tout ce temps sera du temps de perdu que le processeur aura du mal à mitiger. Autant dire que réduire les défauts de cache est beaucoup plus efficace que d'optimiser les calculs effectués par le processeur (erreur courante chez de nombreux programmeurs, notamment débutants).
===L'impact de la taille du cache sur le taux de défaut et la latence===
Il y a un lien entre taille du cache, taux de défaut, débit binaire et latence moyenne. Globalement, plus un cache est gros, plus il est lent. Simple application de la notion de hiérarchie mémoire vue il y a quelques chapitres. Les raisons à cela sont nombreuses, mais nous ne pouvons pas les aborder ici, car il faudrait que nous sachions comment fonctionne un cache et ce qu'il y a à l'intérieur, ce qui sera vu dans la suite du chapitre. Toujours est-il que la latence moyenne d'un cache assez gros est assez importante. De même, le débit binaire d'un cache diminue avec sa taille, mais dans une moindre mesure. Les petits caches ont donc un gros débit binaire et une faible latence, alors que c'est l'inverse pour les gros caches.
Une grande capacité de cache améliore le taux de succès, mais cela se fait au détriment de son temps de latence et de son débit, ce qui fait qu'il y a un compromis assez difficile à trouver entre taille du cache, latence et débit. Il peut arriver qu'augmenter la taille du cache augmente son temps d'accès au point d’entraîner une baisse de performance. Par exemple, les processeurs Nehalem d'Intel ont vus leurs performances dans certains jeux vidéos baisser de 2 à 3 %, malgré de nombreuses améliorations architecturales, parce que la latence du cache L1 avait augmentée de 2 cycles d'horloge.
Pour avoir une petite idée du compromis à faire, regardons la relation entre taille du cache et taux de défaut. Il existe une relation approximative entre ces deux variables, appelée la '''loi de puissance des défauts de cache'''. Elle donne le nombre total de défaut de cache en fonction de la taille du cache et de deux autres paramètres. Voici cette loi :
: <math>\text{Taux de défauts de cache} \approx K \times \text{Taille du cache}^{- \alpha }</math>, avec <math>K</math> et <math>\alpha</math> deux coefficients qui dépendent du programme exécuté.
Le coefficient <math>\alpha</math> est généralement compris entre 0.3 et 0.7, guère plus, et varie suivant le programme exécuté. Précisons que cette loi ne marche que si le cache est assez petit par rapport aux données à utiliser. Pour un cache assez gros et des données très petites, la relation précédente est mise en défaut. Pour s'en rendre compte, il suffit d'étudier le cas extrême où toutes les données nécessaires tiennent dans le cache. Dans ce cas, il n'y a qu'un nombre fixe de défauts de cache : autant qu'il faut charger de données dans le cache. Le nombre de défauts de cache observé dans cette situation n'est autre que le coefficient <math>K</math> de la situation précédente, mais il n'y a aucune dépendance entre taux de défaut et taille du cache.
L'origine de cette relation s'explique quand on regarde combien de fois chaque donnée est réutilisée lors de l’exécution d'un programme. La plupart des données finissent par être ré-accédées à un moment ou un autre et il se passe un certain temps entre deux accès à une même donnée. Sur la plupart des programmes, les observations montrent que beaucoup de réutilisations de données se font après un temps très court et qu'inversement, peu de ré-accès se font après un temps inter-accès long. Si on compte le nombre de réutilisation qui ont un temps inter-accès bien précis, on retrouve une loi de puissance identique à celle vue précédemment :
: <math>\text{Nombre de réaccès avec un temps inter-accès égal à t} \approx K \times t^{- \beta}</math>, avec t le temps moyen entre deux réutilisations.
Le coefficient <math>\beta</math> est ici compris entre 1.7 et 1.3. De manière générale, les coefficients <math>\alpha</math> et <math>\beta</math> sont reliés par la relation <math>\alpha = 1 - \beta</math>, ce qui montre qu'il y a un lien entre les deux relations.
Précisons cependant que la loi de puissance précédente ne vaut pas pour tous les programmes informatiques, mais seulement pour la plupart d’entre eux. Il n'est pas rare de trouver quelques programmes pour lesquels les accès aux données sont relativement prédictibles et où une bonne optimisation du code fait que la loi de puissance précédente n'est pas valide.
La loi de puissance des défauts de cache peut se démontrer à partir de la relation précédente, sous certaines hypothèses. Si un suppose que le cache est assez petit par rapport aux données, alors les deux relations sont équivalentes. L'idée qui se cache derrière la démonstration est que si le temps entre deux accès à une donnée est trop long, alors la donnée accédée aura plus de chance d'être rapatriée en RAM, ce qui cause un défaut de cache. La chance de rapatriement dépend de la taille du cache, un cache plus gros peut conserver plus de données et a donc un temps avant rapatriement plus long.
==Les lignes de cache et leurs tags==
Du point de vue du processeur, les lectures et écritures se font mot mémoire par mot mémoire. Un processeur avec des entiers de 64 bits recoit des données de 64 bits de la part du cache, et y écrit des mots de 64 bits. Mais quand on regarde comment sont stockées les données à l'intérieur du cache, les choses sont différentes.
===Les lignes de cache===
Les données sont mémorisées dans le cache par blocs de plusieurs bytes, d'environ 64 à 256 octets chacun, qui portent le nom de '''lignes de cache'''. Les lignes de cache sont l'unité de stockage que l'on trouve à l'intérieur du cache, mais elles servent aussi d'unité de transaction avec la mémoire RAM. Sur les caches actuels, on transfère les données entre le cache et la RAM ligne de cache par ligne de cache, dans la limite de la taille du bus mémoire. Mais d'autres caches plus anciens permettaient de faire des transferts plus fins. C’est-à-dire qu'on pouvait mettre à jour quelques octets dans une ligne de cache sans avoir à la recopier intégralement depuis ou dans la mémoire RAM.
En théorie, on pourrait imaginer des caches où les données sont stockées différemment, où l'unité serait le mot mémoire, par exemple. Par exemple, sur un processeur 64 bits, on aurait une ligne de cache de 64 bits. Cela aurait l'avantage de la simplicité : les transferts entre le processeur et la mémoire serait de même taille, l'intérieur du cache ressemblerait à son interface montrée au processeur. Mais cela aurait quelques défauts qui sont compensés par l'organisation en lignes de cache de grande taille.
Le premier avantage des lignes de cache est lié à la localité spatiale, la tendance qu'on les programmes à accéder à des données proches les unes des autres. Des accès mémoires consécutifs ont tendance à se faire à des adresses proches, qui ont de bonnes chances d'être dans la même ligne de cache. Et des accès consécutifs à une même ligne de cache sont plus rapides que des accès à deux lignes distinctes. Une autre raison est tout simplement que cela simplifie considérablement la circuiterie du cache. Pour une capacité identique, il vaut mieux avoir peu de lignes de cache assez grosses, que beaucoup de petites lignes de cache. La raison est que les circuits du cache, comme le décodeur, l'encodeur et autres, ont moins de sorties et sont donc plus simples.
===L'alignement des lignes de cache===
Les lignes de cache sont des blocs de plusieurs dizaines à centaines de bytes, dont la taille est presque toujours une puissance de deux. De plus, les lignes de cache sont alignées en mémoire. Nous avions déjà abordé la notion d'alignement mémoire dans un chapitre précédent, mais le concept d'alignement des lignes de cache est quelque peu différent. Quand nous avions parlé d'alignement auparavant, il s'agissait de l'alignement des données manipulées par le processeur, qui faisait partie du jeu d'instruction du processeur. Ici, nous parlons d'un alignement totalement différent, invisible pour le programmeur, sans lien avec le jeu d’instruction. Voyons de quoi il retourne.
Concrètement, cela veut dire que du point de vue du cache, la RAM est découpée en blocs qui font la même taille qu'une ligne de cache, aux positions prédéterminées, sans recouvrement entre les blocs. Par exemple, pour un cache dont les lignes de cache font 256 octets, le premier bloc est à l'adresse 0, le second est 256 octets plus loin, c'est à dire à l'adresse 256, le troisième à l'adresse 512, la quatrième à l'adresse 768, etc. Une ligne de cache de 256 octets contiendra une donnée provenant d'un bloc de RAM de 256 octets, dont l'adresse est systématiquement un multiple de 256. Il n'est pas possible qu'une ligne de cache contienne un bloc de 256 octets dont l'adresse du premier octet serait l'adresse 64, ou l'adresse 32, par exemple. En clair, les adresses de ces blocs sont des multiples de la taille de la ligne de cache, de la taille des blocs. Cela rappelle les contraintes d'alignement vues dans le chapitre "Le modèle mémoire : alignement et boutisme", mais appliquées aux lignes de cache.
L'alignement des lignes de cache a des conséquences pratiques pour la conception des caches. Notons qu'il est en théorie possible d'avoir des caches dont les lignes de cache ne sont pas alignées, mais cela poserait des problèmes majeurs. Il serait en effet possible qu'une donnée soit présente dans deux lignes de cache à la fois. Par exemple, prenons le cas où une ligne de cache de 256 commence à l'adresse 64 et une autre ligne de cache commence à l'adresse 0. L'adresse 128 serait dans les deux lignes de cache ! Et cela poserait des problèmes lors des lectures, mais encore plus lors des écritures. C'est pour éviter ce genre de problèmes que les lignes de cache sont alignées avec la mémoire RAM dans tous les caches existants.
L'alignement des lignes de cache est une chose que les programmeurs doivent parfois prendre en compte quand ils écrivent du code ultra-optimisé, destiné à des programmes demandant des performances extrêmes. Il arrive que les contraintes d'alignement posent des problèmes. Nous avions vu dans le chapitre sur le boutisme et l'alignement qu'il valait mieux gérer l'alignement des variables des structures de données, pour éviter les accès non-alignés avec le bus mémoire. La même chose est possible, mais pour l'alignement avec des lignes de cache. Si une données, comme un entier ou un flottant, est à cheval sur deux lignes de cache, elle sera chargée en deux fois, en deux lectures. idem pour les écritures.
Typiquement, l'idéal est que, pour une structure de donnée, on puisse en mettre un nombre entier dans une ligne de cache. Ou alors, si la structure est vraiment grande, que celle-ci occupe un nombre entier de lignes de cache. Si ce n'est pas le cas, il y a un risque d'accès non-alignés, c'est à dire qu'une structure se retrouve à cheval sur deux lignes de cache, avec les défauts que cela implique.
===Le tag d'une ligne de cache===
Plus haut, nous avions dit que le cache mémorise, pour chaque ligne de cache, l'adresse RAM associée. Le cache contient donc des paires adresse-ligne de cache qui lui permettent de faire le lien entre ligne de cache et adresse. Mais du fait de l'organisation du cache en lignes de cache de grande taille, qui sont de plus alignées en mémoire, il faut nuancer cette affirmation. Le cache ne mémorise pas la totalité de l'adresse, ce qui serait inutile. L'alignement des lignes de cache en RAM fait que les bits de poids faible de l'adresse ne sont pas à prendre en compte pour l'association adresse-ligne de cache. Dans ces conditions, on mémorise seulement la partie utile de l'adresse mémoire correspondante, qui forme ce qu'on appelle le '''tag'''.
Le reste de l'adresse indique quelle est la position de la donnée dans la ligne de cache. Par exemple, prenons le cas où le processeur gère des nombres entiers de 64 bits (8 octets) et des lignes de cache de 128 octets : chaque ligne de cache contient donc 16 entiers. Si le processeur veut lire ou écrire un entier bien précis, il doit préciser sa place dans la ligne de cache. Et ce sont les bits de l'adresse mémoire non-inclus dans le cache qui permettent de faire ça. En clair, une adresse mémoire à lire/écrire est interprété par le cache comme la concaténation d'un tag et de la position de la donnée dans la ligne de cache correspondante.
[[File:Adressage d'un cache totalement associatif.png|centre|vignette|upright=2|Adressage d'un cache totalement associatif]]
Le cache est donc une grande table de correspondance entre tags et lignes de cache. Lors d'un accès mémoire, le cache extrait le tag de l'adresse à lire ou écrire, et le compare avec les tags de chaque ligne de cache. Si une ligne contient ce tag, alors c'est que cette ligne correspond à l'adresse, et c'est un défaut de cache sinon. Lors d'un succès de cache, la ligne de cache est lue depuis le cache et envoyée à un multiplexeur qui sélectionne la donnée à lire dans la ligne de cache. Le fonctionnement est similaire pour une écriture : la donnée à écrire passe dans un démultiplexeur, qui envoie la donnée au bon endroit dans la ligne de cache sélectionnée.
[[File:Lecture d'une donnée dans un cache CPU, organisé en lignes de cache.png|centre|vignette|upright=2|Lecture d'une donnée dans un cache CPU, organisé en lignes de cache.]]
===Le contenu d'une ligne de cache===
Dans ce qui va suivre, nous allons considérer que chaque ligne de cache mémorise son tag, les données de la ligne de cache proprement dit, et quelques bits de contrôle annexes qui varient suivant le cache considéré.
[[File:Tag d'une ligne de cache.png|centre|vignette|upright=2|Tag d'une ligne de cache.]]
Les caches modernes incluent de nombreux bits de contrôle, mais deux d'entre eux sont communs à presque tous les caches modernes : le bit ''Dirty'' et le bit ''Valid''.
Le '''bit ''Valid''''' indique si la ligne de cache contient des données valides ou non. Si le bit ''Valid'' est à 0, la ligne de cache est en état valide, à savoir qu'elle contient des données et n'est pas vide. Par contre, si ce bit est à 1, la ligne de cache est invalide et son contenu ne peut pas être lu ou écrit. L'utilité de ce bit est qu'il permet d'effacer une ligne de cache très rapidement : il suffit de mettre ce bit à 0. Il existe des situations où le cache doit être effacé, on dit alors qu'il est invalidé. Une section de ce chapitre sera dédié à l'invalidation du cache.
Le '''bit ''Dirty''''' indique qu'une ligne de cache a été modifiée. Par modifiée, on veut dire que le processeur a écrit dedans, qu'il a modifié la ligne de cache. Mais attention : si la donnée a été modifiée dans le cache, la modification n'est pas forcément propagée en mémoire RAM. Le bit ''dirty'' indique si c'est le cas, si l'écriture a été propagée en mémoire RAM. Il précise que la ligne de cache contient des données modifiées, alors que la RAM a des données initiales non-modifiées. Une ligne de cache avec un bit ''dirty'' à 1 est dite ''dirty'', par métonymie. Nous verrons cela en détail dans la section sur les caches ''write-back'' et ''write-through''.
Les caches modernes ajoutent des '''bits de détection/correction d'erreur''' dans les bits de contrôle. Pour rappel, les codes de détection/correction d'erreur permettent de se prémunir contre des erreurs matérielles, qui corrompent les données stockées dans une mémoire, ici une mémoire cache. Ils ajoutent un ou plusieurs bits à la ligne de cache, dans les bits de contrôle. Nous reviendrons dessus dans une section ultérieur de ce chapitre.
Sur certains caches assez anciens, on pouvait transférer les lignes de caches morceaux par morceaux. Ces caches avaient des lignes de cache divisées en sous-secteurs, ces sous-secteurs étant des morceaux de ligne de cache qu'on pouvait charger indépendamment les uns des autres (mais qui sont consécutifs en RAM). Chaque secteur avait ses propres bits de contrôle, mais le tag était commun à tous les secteurs.
[[File:Cache à secteurs.png|centre|vignette|upright=2.5|Cache à secteurs.]]
: Dans ce qui va suivre, le terme "ligne de cache" désignera soit un bloc de données copiées depuis la RAM d'une taille de 64/128/256/... octets, soit la concaténation de ces données avec le tag et des bits de contrôle. Les deux définitions ne sont pas équivalentes, mais l'usage a entériné cet abus de langage. Et il faut avouer que cela rend les explications du chapitre plus simples.
==Les instructions de contrôle du cache==
Plus haut, nous avions dit que le cache est totalement transparent du point de vue du programmeur. Le cache contient des copies de données en RAM, le programmeur n'a rien à faire pour utiliser le cache correctement. Mais la réalité est que pour des raisons diverses, des processeurs incorporent des '''instructions de contrôle du cache'''. Il s'agit d’instructions qui agissent sur le contenu du cache. Elles existent pour des raisons diverses qu'on détaillera plus bas, mais il s'agit globalement d'une question de performances ou de nécessité pour le système d'exploitation.
===Les instructions de préchargement===
La première instruction de contrôle du cache est une '''instruction de préchargement''', qui demande à charger un bloc de données dans le cache. Elle prend en opérande une adresse mémoire, et le contenu de cette adresse est chargé dans une ligne de cache. Bien sûr, des contraintes d'alignement sont à prendre en compte : on charge un bloc de la même taille qu'une ligne de cache, aligné en mémoire sur la taille du bloc, qui contient l'adresse.
L'instruction de préchargement n'est utile que si l'instruction est exécutée bien avant que la donnée ne soit utilisée/lue/écrite. Cela permet de charger une donnée dans le cache à l'avance, d'où le nom de préchargement donné à cette technique. Mais les processeurs modernes gérent des techniques de préchargement automatique, qui ne requièrent pas d'instructions de préchargement. Le préchargement automatique et les instructions de préchargement sont deux solutions complémentaires, mais qui peuvent se marcher sur les pieds. Nous en reparlerons dans le prochain chapitre, qui sera dédié au préchargement automatique.
Il faut noter que les instructions de préchargement peuvent être ignorées par le processeur. Sous certaines conditions, le processeur peut décider que l'instruction de préchargement ne sera pas exécutée. Par exemple, il ne va pas précharger une donnée déjà présente dans le cache. Ou encore, si le bus mémoire est occupé, il ne va pas exécuter le préchargement, par manque de ressources matérielles.
===Les instructions d'invalidation et de ''flush''===
Les instructions ''flush'' regroupent deux types d'instructions qui sont souvent utilisées en même temps. Il s'agit des instructions d'invalidation et de nettoyage (''clean''). Les deux termes proviennent de la terminologie ARM, il n'y a pas de terminologie standardisé pour les noms de ces instructions.
Dans les grandes lignes, elles permettent de vider le cache, à savoir de rapatrier son contenu en RAM et de réinitialiser le cache à zéro. Elles sont utilisées par le système d'exploitation lors des commutations de contexte, à savoir quand on passe d'un programme à un autre. Elles sont aussi utilisées lors des appels systèmes et routines d'interruption/exception. L'idée est de vider le cache avant d'exécuter un nouveau programme ou une nouvelle routine. Le nouveau programme aura accès à un cache tout propre, les données de l'ancien programme auront été retirée du cache.
Les '''instructions ''clean''''' recopient le contenu de la ligne de cache en RAM. Elles forcent la recopie immédiatement de la ligne de cache en mémoire RAM. Pour faire leur travail, elle vérifient si la ligne de cache a été modifiée, avant de la recopier en RAM. Et pour cela, ils vérifient le bit de contrôle ''dirty'', qui est mis à 1 après une première écriture. Si ce bit est à 0, alors pas besoin de recopier la ligne de cache : elle n'a pas été modifiée, la RAM a déjà la bonne copie. Mais s'il est à 1, le cache et la RAM n'ont pas le même contenu, la recopie s'exécute.
Les '''instructions d'invalidation''' permettent d'invalider une ligne de cache, à savoir d'effacer son contenu. Nous verrons à quoi servent ces instructions dans la section sur les changement de processus. Invalider une ligne de cache est une opération optimisée : le cache n'est en réalité pas réellement effacé. À la place, le bit ''Valid'' de chaque ligne de cache est juste mis à 0. Il faut noter que l'invalidation efface les lignes de cache sans se préoccuper de leur contenu. Elle se moque qu'une ligne de cache contienne une donnée modifiée, ''dirty'' ou quoique ce soit : la ligne de cache est effacée, point.
Il est possible d'invalider une ligne de cache en fournissant une adresse mémoire, mais il est aussi possible d'invalider le cache tout entier. Le choix entre les deux dépend du mode d'adressage de l'instruction d'invalidation. Parfois, il existe une instruction séparée pour invalider tout le cache, et une autre pour invalider une ligne de cache bien précise. Des instructions séparées sont parfois disponibles pour invalider les caches de données et d'instructions, parfois aussi la TLB (un cache qu'on verra dans quelques chapitres). Il est possible de n'invalider que le cache L1, voire le cache L2.
Il faut noter que l'invalidation efface tout le cache, mais ne se préoccupe pas de vérifier si les données ont été modifiées dans le cache. Pour certains caches, comme le cache d'instruction, ce n'est pas un problème, vu qu'il est en "lecture seule". Mais pour les caches de données, les données modifiées sont perdues en cas d'invalidation. Heureusement, il existe des instructions d'invalidation qui fusionnent une instruction ''clean'' et une instruction d'invalidation. Il s'agit d''''instructions d'invalidation spéciales'''.
===Les instructions d'optimisation : instructions non-temporelles et écritures optimisées===
Les '''instructions mémoire non-temporelles''' contournent complètement le cache. Par exemple, une lecture peut lire une donnée, mais celle-ci ne sera pas chargée dans le cache, elle passe directement de la RAM vers les registres. Une section entière de ce chapitre sera dédiée au contournement du cache, à savoir aux situations où les accès mémoire doivent passer directement du processeur à la RAM sans passer par le cache.
D'autres instructions assez rares incorporent des indications pour le cache. Par exemple, l'instruction ''load last'' des processeurs POWER PC implique que la donnée ne sera utilisée qu'une seule fois. Elle est donc chargée dans le cache, mais la ligne de cache est configurée de manière à être remplacée très rapidement, typiquement avec une valeur de LRU/LFU adéquate. La donnée est bien chargée dans le cache, au cas où elle doive être relue suite à une mauvaise prédiction de branchement ou autre, chose qu'une lecture non-temporelle (qui contourne le cache) ne fait pas. Des indications de ce type sont appelées des '''''cache hint'''''.
L''''instruction ''flush''''' permet de préciser qu'une ligne de cache contient une donnée inutile, qui ne sera pas réutilisée par le programme. Pas besoin de la conserver dans le cache, elle peut laisser sa place à des données plus utiles. Or, sans indication, les algorithmes de remplacement d'une ligne de cache risquent de conserver cette donnée trop longtemps, ce qui entraine une certaine pollution du cache par des données inutiles.
Une autre instruction est elle beaucoup plus importante : celle de '''pré-allocation sur écriture'''. Elle sert dans le cas où une ligne de cache est complétement écrite. Par exemple, imaginons qu'on veuille écrire dans une portion de mémoire. Si celle-ci n'est pas dans le cache, le processeur va charger une ligne de cache complète depuis la RAM, écrire dans la ligne de cache, puis recopier la ligne de cache modifiée en mémoire RAM. Une écriture en RAM demande donc de faire une lecture et une écriture. Mais les instructions de pré-allocation sur écriture permettent de prévenir qu'une ligne de cache sera intégralement écrite, et qu'il n'y a donc pas besoin de lire celle-ci depuis la RAM. Notons que l'instruction d'écriture qui suit n'est pas une écriture non-temporelle, vu que les données sont écrites dans la ligne de cache, qui est ensuite envoyée en mémoire RAM dès que nécessaire. De plus, les données écrites peuvent ensuite être relue depuis le cache si nécessaire.
Enfin, certains processeurs MIPS incorporent une instruction pour modifier le tag d'une ligne de cache. Elles servent à optimiser les copies mémoire, à savoir quand on copie un bloc de données d'un endroit à un autre. L'idée est de charger le bloc de données dans le cache avec une instruction LOAD/PREFETCH, de modifier le tag pour qu'il pointe vers l'adresse à écrire, et de laisser faire le cache pour que l'écriture se fasse en RAM. Mais les contraintes pour utiliser cette instruction sont assez drastiques : les données doivent être alignées sur la taille d'une ligne de cache, le bloc de départ et d'arrivée (l'original versus la copie) ne doivent pas se recouvrir, etc.
==L'associativité des caches et leur adressage implicite==
Lorsqu'on souhaite accéder au cache, il faut trouver quelle est la ligne de cache dont le tag correspond à l'adresse demandée. On peut classifier les caches selon leur stratégie de recherche de la ligne correspondante en trois types de caches : totalement associatifs, directement adressés (''direct mapped'') et associatifs par voie.
===Les caches totalement associatifs===
Avec les caches totalement associatifs, toute donnée chargée depuis la mémoire peut être placée dans n'importe quelle ligne de cache, sans aucune restriction. Ces caches ont un taux de succès très élevé, quand on les compare aux autres caches.
[[File:Cache totalement associatif.png|centre|vignette|upright=2|Cache totalement associatif.]]
Concevoir un cache totalement associatif peut se faire de deux grandes manières différentes. La première consiste tout simplement à combiner une mémoire associative avec une mémoire RAM, en ajoutant éventuellement quelques circuits annexes. La mémoire associative mémorise les tags, alors que la mémoire RAM mémorise les données de la ligne de cache, éventuellement avec quelques bits de contrôle. La ligne de cache est stockée à une adresse A dans la mémoire RAM et son tag est stocké à la même adresse, mais dans la mémoire CAM. Ce faisant, quand on envoie le tag à la mémoire CAM, elle renvoie l'adresse de la ligne de cache dans la mémoire RAM. Cette adresse est alors envoyée directement sur le bus d'adresse de la RAM, et la lecture est effectuée automatiquement. Il faut ajouter quelques circuits annexes pour garantir que les écritures se passent correctement dans les deux mémoires, mais rien de bien terrible.
[[File:Cache fabriqué avec une mémoire associative et une RAM.png|centre|vignette|upright=3|Cache fabriqué avec une mémoire associative et une RAM]]
Il est cependant possible d'optimiser un tel cache, en fusionnant la mémoire CAM et la mémoire RAM, afin d'éliminer des circuits redondants. Pour comprendre pourquoi, rappelons que les mémoires CAM sont composées d'un plan mémoire, d'un paquet de comparateurs et d'un encodeur. Quant à la mémoire RAM, elle est composée d'un décodeur connecté au plan mémoire. En mettant une CAM suivie d'une RAM, on a un encodeur dont l'entrée est envoyée à un décodeur.
[[File:Cache totalement associatif naif.png|centre|vignette|upright=3|Cache totalement associatif naif]]
Or, le décodeur réalise l'opération inverse de l'encodeur, ce qui fait que mettre les deux composants à la suite ne sert à rien. On peut donc retirer l'encodeur et le décodeur, et envoyer directement les résultats des comparateurs sur les entrées de commande du plan mémoire de la RAM.
[[File:Cache totalement associatif optimisé.png|centre|vignette|upright=2|Cache totalement associatif optimisé]]
Avec cette méthode, les circuits du cache ressemblent à ce qui illustré ci-dessous. Le tag est envoyé à chaque ligne de cache. Le tag envoyé est alors comparé avec le Tag contenu dans chaque ligne de cache, comme c'est le cas sur les mémoires associatives. Si une ligne de cache matche avec le tag envoyé en entrée, la ligne pour laquelle il y a eu une égalité est alors connectée sur les lignes de bit (''bitlines''). Cela est réalisé par un circuit commandé par le comparateur de la ligne de cache. Il ne reste plus qu'à sélectionner la portion de la ligne de cache qui nous intéresse, grâce à un paquet de multiplexeurs. Cela permet d'effectuer une lecture ou écriture, mais il faut aussi préciser si il y a eu un défaut de cache ou un succès. Un succès de cache a lieu quand au moins des comparaisons est positive, alors que c'est un défaut de cache sinon. En clair, détecter un succès de cache demande juste de connecter une porte OU à plusieurs entrées à tous les comparateurs.
[[File:Organisation générale d'un cache totalement associatif.png|centre|vignette|upright=2|Organisation générale d'un cache totalement associatif.]]
===Les caches directement adressés===
Les caches directement adressés peuvent être vus comme un cache totalement associatif auquel on aurait ajouté des restrictions assez drastiques. Plus haut, on a vu qu'un cache totalement adressé est équivalent à la combinaison d'une CAM avec une RAM. La mémoire CAM prend en entrée un Tag et traduit celui-ci en une adresse qui commande la mémoire RAM interne au cache. Dans ce qui suit, l'adresse interne au cache sera appelé l''''indice''' pour éviter toute confusion.
[[File:Cache hash table - 2.png|centre|vignette|upright=2|Fonctionnement interne du cache, expliquée sous forme abstraite, en utilisant la notion d'indice interne au cache.]]
Les caches directement adressés cherchent à remplacer la mémoire CAM par un circuit combinatoire. Ce circuit traduit le Tag en indice, mais est beaucoup plus simple qu'une mémoire CAM. Mais qui dit circuit plus simple dit circuit plus limité. Un circuit combinatoire n'est pas aussi versatile que ce qui est permis avec une mémoire CAM. En conséquence, une restriction majeure apparait : toute adresse mémoire est associée dans une ligne de cache prédéfinie, toujours la même. L'association entre ligne de cache et adresse mémoire est faite par le circuit combinatoire, et ne peut pas changer.
Les concepteurs de caches s'arrangent pour que des adresses consécutives en mémoire RAM occupent des lignes de cache consécutives, par souci de simplicité. Tout se passe comme suit la mémoire RAM était découpés en blocs de la même taille que le cache. La première adresse du bloc est associée à la première ligne de cache (celle d'indice 0), la seconde adresse est associée à la seconde adresse du_ bloc, et ainsi de suite. Le tout est illustré ci-dessous.
[[File:Cache adressé directement.png|centre|vignette|upright=2|Cache adressé directement.]]
Avec cette contrainte, le circuit de traduction de l'adresse en adresse mémoire pour la RAM interne au cache est drastiquement simplifié, et disparait même. Une partie de l'adresse mémoire sert à indiquer la position de la donnée dans le cache, le reste de l'adresse sert encode le tag et la position de la donnée dans le ligne de cache.
[[File:Cache line.png|centre|vignette|upright=2|Adresse d'une ligne de cache sur un cache adressé directement.]]
Un cache directement adressé est conçu avec une RAM, un comparateur, et un paquet de multiplexeurs. En général, la mémoire RAM stocke les lignes de caches complète. Il arrive que l'on utilise deux mémoires RAM : une pour les tags et une pour les données, mais cette technique augmente le nombre de circuits et de portes logiques nécessaires, ce qui réduit la capacité du cache. L'index à lire/écrire est envoyé sur l'entrée d'adresse de la RAM, la RAM réagit en mettant la ligne de cache sur sa sortie de donnée. Sur cette sortie, un comparateur compare le tag de la ligne de cache lue avec le tag de l'adresse à lire ou écrire. On saura alors si on doit faire face à un défaut de cache. Ensuite, un multiplexeur récupère la donnée à lire/écrire.
[[File:Direct mapped cache - french.png|centre|vignette|upright=2|Cache directement adressé.]]
L'accès à un cache directement adressé a l'avantage d'être très rapide vu qu'il suffit de vérifier une seule ligne de cache : celle prédéfinie. Mais ces caches ne sont cependant pas sans défauts. Vu que le cache est plus petit que la mémoire, certaines adresses mémoires se partagent la même ligne de cache. Si le processeur a besoin d’accéder fréquemment à ces adresses, chaque accès à une adresse supprimera l'autre du cache : tout accès à l'ancienne adresse se soldera par un défaut de cache. Ce genre de défauts de cache causés par le fait que deux adresses mémoires ne peuvent utiliser la même ligne de cache s'appelle un '''défaut par conflit''' (''conflict miss''). Les défauts par conflit n'existent pas sur les caches totalement associatifs. En conséquence, le taux de succès des caches directement adressés est assez faible comparé aux autres caches.
[[File:Cache Block Basic Conflict.svg|centre|vignette|upright=1.5|Exemple de ''Conflict Miss''.]]
===Les caches associatifs par voie===
Les caches associatifs par voie sont un compromis entre les caches directement adressés et les caches totalement associatifs. Pour simplifier, ces caches sont composés de plusieurs caches directement adressés accessibles en parallèle, chaque cache/RAM étant appelé une '''voie'''. Avec ces caches, toute adresse mémoire en RAM est associée à une ligne de cache dans chaque voie.
[[File:Cache associatif par voie.png|centre|vignette|upright=2|Cache associatif par voie.]]
Le schéma ci-dessous compare un cache directement adressé et un cache associatif à deux voies. On voit que chaque adresse est associée à une ligne de cache bien précise avec un cache directement dressé, et à deux lignes de cache avec un cache associatif à deux voies. L'adresse sera associée à 4 lignes de cache sur un cache associatif à 4 voies, à 8 lignes pour un cache à 8 voies, etc. L'ensemble des lignes de cache associées à une adresse est appelé un '''ensemble'''.
[[File:Cache Fill.svg|centre|vignette|upright=2|Comparaison entre un cache directement adressé et un cache associatif à deux voies.]]
Sur ces caches, toute adresse est découpée en trois parties : un tag, un index, et un décalage, comme sur les caches directement adressés. Comme vous pouvez le voir, l'organisation est identique à celle d'un cache totalement associatif, à part que chaque ensemble tag-ligne de cache est remplacé par une mémoire RAM qui en contient plusieurs.
[[File:Implémentation d'un cache associatif par voie.png|centre|vignette|upright=2|Implémentation d'un cache associatif par voie.]]
Le risque de conflits d'accès au cache est donc réduit sur un cache associatif à plusieurs voies, et il est d'autant plus réduit que le cache a de voies. Par contre, leur conception interne fait qu'ils ont un temps d'accès légèrement élevé que les caches directement adressés. Les caches associatifs par voie ont donc un taux de succès et un temps d'accès intermédiaire, situé entre les caches directement adressés et totalement associatifs. Ils sont une sorte de compromis entre réduction des défaut par conflits d'accès au cache et temps d'accès, et complexité des circuits.
==Les optimisations des caches associatifs par voie==
Les caches partiellement associatifs regroupent les caches associatifs par voie et directement adressés, ainsi que leurs variantes. En clair : tous les caches qui ne sont pas totalement associatifs. Ils peuvent être optimisés de nombreuses manières, que ce soit pour gagner en performance ou pour économiser de l’énergie. Dans cette section, nous allons voir quelles sont ces optimisations.
===Les caches pseudo-associatifs===
Les caches adressés par voie contiennent une mémoire SRAM par voie. En théorie, les voies sont accédées en parallèles, en même temps, afin de voir si l'on a un succès de cache ou un défaut. Les '''caches pseudo-associatifs''' sont identiques aux caches associatifs par voie, si ce n'est qu'ils vérifient chaque voie une par une. Ils ont été utilisés sur des processeurs commerciaux, un exemple étant l'IBM 370.
Là encore, on perd en performance pour gagner en consommation d'énergie. Le temps d'accès dans le meilleur des cas est plus faible pour les caches pseudo-associatifs, mais le pire des cas teste tous les caches avant de tomber sur le bon. Les performances sont donc réduites. Mais la consommation énergétique est meilleure, vu qu'on ne vérifie pas forcément toutes les voies en parallèle. On teste la première voie, éventuellement la seconde, peut-être la troisième, etc. Mais dans le cas général, on ne teste qu'une partie des voies, pas toutes, ce qui donne un gain en termes d'énergie.
L'implémentation de caches de ce genre demande que l'on parcoure les voies une par une, en commençant de la première jusqu'à la dernière. Pour cela, un simple compteur suffit. Suivant la valeur du compteur, la voie associée est activée puis accédée. Toute la complexité revient à ajouter un circuit qui prend la valeur du compteur, et active la voie associée, lance un accès mémoire dessus. Vu que les voies sont chacune des caches ''direct mapped'', il suffit pour cela de geler les entrées d'adresse, soit en les déconnectant, soit en utilisant du ''clock gating'' ou de l'évaluation gardée. Les détails d'implémentation, non-cités ici, varient selon le cache.
===La prédiction de voie===
Pour réduire le temps d'accès des caches pseudo-associatifs, certains chercheurs ont inventé la '''prédiction de voie''', qui consiste à faire des paris sur la prochaine voie accédée. L'idée est d'accéder à la voie qui contient la donnée voulue du premier coup, en lisant celle-ci en priorité.
Dans son implémentation la plus simple, le cache reste un cache pseudo-associatif. Lors d'un accès au cache, les voies sont toutes parcoures une par une. Par contre, les voies ne sont donc pas parcourues de la première vers la dernière, mais dans un ordre différent. Cette technique permet de mettre en veille les voies sur lesquels le processeur n'a pas parié, ce qui permet de diminuer la consommation énergétique du processeur. C'est plus efficace que d'aller lire plusieurs données dans des voies différentes et de n'en garder qu'une. L'implémentation est assez simple : il suffit d'ajouter un circuit de prédiction de voie,relié au compteur de voie.
Une amélioration de la technique fait fonctionner le cache comme un intermédiaire entre cache pseudo-associatif et associatif par voies. L'idée est de chercher la voie prédite en premier, puis de chercher dans toutes les voies en parallèle en cas de défaut de cache. Au lieu d'attendre que les comparaisons de tags donnent leur résultat, le processeur sélectionne automatiquement une voie et configure les multiplexeurs à l'avance. Si le processeur ne se trompe pas, le processeur accède à la donnée plus tôt que prévu. S'il se trompe, le processeur annule la lecture effectuée en avance et recommence en faisant un accès en parallèle aux autres voies. Le compromis entre performance et consommation d'énergie est alors différent. On économise de l'énergie par rapport à un cache associatif par voie, au prix d'une petite perte de performance (doublement des temps d'accès). Mais par rapport à un cache pseudo-associatif, l'économie d'énergie est bien moindre, au prix d'un gain en performance assez manifeste.
Prédire quelle voie sera la bonne est assez simple. En vertu du principe de localité, les accès futurs ont des chances de tomber dans les voies les plus fréquemment utilisées ou dans celle plus récemment utilisée. Il suffit de retenir la voie la plus récemment accédée dans un registre, qui sera utilisée comme prédiction. Pour vérifier que la prédiction est correcte, il suffit de comparer le registre et le résultat obtenu après vérification des tags.
Cependant, on peut complexifier l'implémentation pour prendre en compte l'adresse à lire/écrire, l'instruction à l'origine de l'accès mémoire ou tout autre paramètre utile. Par exemple, des instructions différentes ont tendance à aller chercher leurs données dans des ensembles différents et la voie à choisir n'est pas la même. Pour cela, il suffit d'utiliser un cache pour stocker la correspondance instruction - voie. Pour plus de simplicité, la mémoire cache des prédictions est parfois remplacée par une RAM, qui est adressée :
* soit par le program counter de l'instruction à l'origine de l'accès (en réalité, seulement quelques bits de poids faible de l'adresse) ;
* soit par l'adresse à accéder (là encore, quelques bits de poids faible) ;
* soit (pour les modes d'adressage qui utilisent un registre de base et un décalage) par un XOR entre les bits de poids faible de l'adresse de base et le décalage ;
* soit par autre chose.
===La mise en veille sélective des voies===
Les caches associatifs ont tendance à utiliser beaucoup d'énergie, même quand on n'y accède pas. Aussi, certains processeurs détectent quand le cache est peu utilisé et en profitent pour mettre en veille les voies inutilisées. Vous vous demandez certainement ce qui se passe quand une donnée à lire/écrire est dans une voie désactivée. La réponse est que le cache détecte cette situation, car elle déclenche un succès de cache. Les ''tags'' ne sont en effet pas désactivés, seules les données sont mises en veille. L'implémentation est plus simple sur les caches qui séparent les tags et les données dans deux RAM différentes.
Cette optimisation marche surtout sur les gros caches, qui ont des chances d'avoir une portion significative d’inutilisée (pas assez de données pour les remplir), donc généralement les caches L3/L4. Par exemple, les processeurs d'Intel de microarchitecture Ivy Bridge disposent d'un cache de 8 mébioctets à 16 voies, qu'ils peuvent faire passer à 512 kibioctets si le besoin s'en fait sentir. Quand ces processeurs détectent une faible activité, ils mettent en veille 14 voies et n'en gardent que 2 d'actives. Évidemment, les 14 voies sont vidées avant d'être mises en veille, afin qu'une aucune donnée ne soit perdue.
===Les caches ''skew-associative''===
Vous aurez remarqué que dans une voie, les lignes sont accédées en adressage direct : les défauts par conflit sont possibles sur un cache associatif par voie. Pour éviter cela, certains chercheurs ont créé des '''caches ''skew associative''''' (ou associatifs à biais).
Pour faire simple, les index des lignes de cache subissent un petit traitement avant d'être utilisés. Le traitement en question est différent suivant la voie de destination, histoire que deux adresses mémoires avec des index identiques donnent des index différents après traitement. Le traitement en question est souvent une permutation des bits de l'index, qui est différente suivant la voie prise, ou un simple XOR avec un nombre qui dépend de la voie.
[[File:Implémentation d'un cache skew associative.jpg|centre|vignette|upright=2|Implémentation d'un cache skew associative.]]
==L'adressage physique ou logique des caches==
Le cache utilise les adresses à lire/écrire pour déterminer s'il a une copie de la donnée en son sein. Mais l’interaction entre caches et mémoire virtuelle donne lieu à un petit problème : l'adresse utilisée est-elle une adresse virtuelle/logique ou physique ? La réponse varie suivant le processeur : certains caches utilisent l'adresse virtuelle, tandis que d'autres prennent l'adresse physique. On parle de cache '''virtuellement tagué''' dans le premier cas et de cache '''physiquement tagué''' dans le second.
{|
|[[File:Cache tagué virtuellement.png|vignette|Cache tagué virtuellement.]]
|[[File:Cache tagué physiquement.png|vignette|Cache tagué physiquement.]]
|}
===L'accès à un cache physiquement/virtuellement tagué===
La manière d'accéder à un cache dépend de s'il est virtuellement ou physiquement tagué. Il faut utiliser l'adresse virtuelle pour les premiers, physique pour les seconds.
Avec un cache virtuellement tagué, l'adresse logique peut être envoyée directement au cache. La MMU ne traduit les adresses que s'il faut accéder à la mémoire RAM. Ces caches sont donc plus rapides.
Avec un cache physiquement tagué, le processeur doit traduire l'adresse logique en adresse physique dans la MMU, avant d'accéder au cache. La traduction d'adresse se fait soit en accédant à une table des pages en mémoire RAM, soit en accédant à un cache spécifiquement dédié à accélérer la traduction d'adresse, la TLB (''Translation Lookaside Buffer''). Dans la quasi-totalité des cas, la traduction d'adresse passe par la TLB, ce qui fait qu'elle est raisonnablement rapide. Toujours est-il que chaque accès au cache demande d'accéder à la TLB et de faire la traduction d'adresse avant d'accéder au cache. L'accès est donc plus lent que sur les caches virtuellement tagués, où les accès sont plus directs.
[[File:Virtual and Physical addressing.svg|centre|vignette|upright=2|Cache tagué virtuellement versus physiquement tagué.]]
===Les défauts des caches virtuellement tagués===
Les caches physiquement tagués sont moins rapides que les caches virtuellement adressés. Pourtant, les caches virtuellement tagués sont peu fréquents sur les processeurs modernes. Et la raison est assez intéressante : c'est une question d'adresses homonymes et synonymes.
====Les droits d'accès doivent être vérifiés lors d'un accès au cache====
Un premier problème est que la protection mémoire est compliquée avec de tels caches. Rappelons que certaines portions de mémoire sont accessibles seulement en lecture, ou sont interdites en écriture, sont inexécutables, etc. Ces droits d'accès sont gérés par la MMU, qui vérifie pour chaque accès mémoire que l'accès est autorisé. En bypassant la MMU, l'accès au cache virtuellement tagué ne permet pas de faire ces vérifications. Il est possible de charger une donnée en lecture seule dans le cache, mais d'y faire des accès en écriture pour les accès ultérieurs.
Les solutions à cela sont multiples. La première consiste à consulter la MMU en parallèle de l'accès au cache. L'accès au cache est alors réalisé de manière spéculative, et est ensuite confirmé/annulé une fois que la MMU a rendu son verdict. Les performances du cache restent alors les mêmes : l'accès à la MMU se fait en parallèle de l'accès au cache, pas avant. Une autre solution est d'ajouter les droits d'accès en question dans la ligne de cache, dans les bits de contrôle situés après le Tag. Chaque accès au cache récupère ces bits de contrôle et vérifie si l'accès est autorisé. L'inconvénient est que les lignes de cache deviennent plus longues, les droits d'accès sont dupliqués entre MMU et cache. Mais si le budget en transistor suit, ce n'est rien d'insurmontable.
====Les adresses homonymes perturbent la gestion du cache====
Pour rappel, une adresse logique homonyme correspond à plusieurs adresses physiques différentes. Elles surviennent quand chaque programme a son propre espace d'adressage. Dans ce cas, une adresse logique correspondra à une adresse physique différente par programme.Une autre manière de voir les choses est qu'il y a en réalité deux adresses homonymes, qui ont la même valeur, mais appartiennent à des espaces d'adressage différentes. Et c'est cette seconde interprétation que nous allons utiliser.
Les caches doivent gérer ces adresses homonymes et faire en sorte que la lecture/écriture d'une adresse homonyme se fasse à la bonne adresse physique, dans la bonne ligne de cache. Et autant un cache physiquement tagué n'a aucun problème avec ça, vu qu'il ne gère que des adresses physiques, autant des problèmes surviennent avec les caches virtuellement tagués. Le problème est que les caches virtuellement tagués doivent faire la différence entre deux adresses homonymes de même valeur.
Pour corriger ces problèmes, il existe deux grandes méthodes. La première méthode est simple : '''vider les caches''' en changeant de programme. Leur contenu est rapatrié en mémoire RAM, puis les caches sont remis à zéro. Le vidage du cache recopie les lignes de cache ''dirty'' (modifiées) en RAM, puis efface/invalide tout le cache. C'est à cela que servent les instructions ''clean'' et d'invalidation vues plus haut, elles ont été inventées pour cette situation précise. Lorsque le système d'exploitation déclenche une commutation de contexte, à savoir qu'il change le programme en cours d'exécution, le processeur vide tous les caches du processeur. Les interruptions font la même chose, elles vide tous les caches du processeur.
Une seconde méthode numérote chaque programme en cours d'exécution, chaque processus. Le numéro attribué est spécifique à chaque processus, ce qui fait qu'il est appelé un '''identifiant de processus CPU'''. Le processeur mémorise l'identifiant du programme en cours d'exécution dans un registre dédié. L'identifiant de processus CPU est utilisé lors des accès mémoire. Chaque ligne de cache contient le numéro de l'espace d'adressage associé, dans son ''tag''. Lors de chaque accès mémoire, l'ID du registre est comparé à l'ID de la ligne de cache accédée, pour vérifier que l'accès mémoire accède à la bonne donnée. Cette méthode n'est pas très économe en termes de transistors.
L'usage d'identifiant de processus CPU est clairement meilleure en termes de performance, les commutations de contexte sont plus rapides. Par contre, le budget en transistor est plus important. Un autre défaut de cette méthode est que l'identifiant de processus est généralement codé sur une dizaine de bits, alors que le système d'exploitation utilise des identifiants de processus beaucoup plus larges, de 32 à 64 bits sur les CPU 32/64 bits. L'OS doit gérer la correspondance entre identifiants de processus CPU et ceux de l'OS. Parfois, pour cette raison, les OS n'utilisent pas toujours ce système d'identifiant de processus CPU.
====Les adresses synonymes perturbent aussi la gestion du cache====
La gestion des adresses synonymes est aussi un gros problème sur les caches virtuellement tagués. Pour rappel, il s'agit du cas où des adresses logiques différentes pointent vers la même adresse physique. Typiquement, quand deux programmes se partagent un morceau de mémoire, ce morceau correspondra à des adresses synonymes dans les deux espaces d'adressage. Mais il arrive que l'on ait des adresses synonymes dans le même espace d'adressage, ce n'est pas si rare !
Autant les adresses synonymes ne posent aucun problème avec les caches physiquement tagués, ce n'est pas le cas avec les caches virtuellement adressés. Sur ces caches, deux adresses logiques synonymes vont tomber dans deux lignes de cache différentes. Corriger ce problème demande d'ajouter des circuits annexes pour détecter les adresses synonymes, qui sont vraiment complexes et ont un cout en termes de performance. Aussi, les caches virtuellement tagués sont très peu utilisés sur les processeurs modernes.
===Les caches virtuellement adressés, mais physiquement tagués===
Si les caches physiquement et virtuellement tagués ont des défauts, il existe un intermédiaire qui est un bon compromis entre ces deux extrêmes. Il s'agit des '''caches virtuellement adressés - physiquement tagués''', aussi appelés '''caches pseudo-virtuels'''. Pour comprendre comment ils fonctionnent, précisons que ces caches sont soit des caches ''direct-mapped'', soit des caches associatifs par voie (composés de plusieurs RAM ''direct-mapped'' accédées en parallèle, plusieurs voies).
L'accès à ce genre de cache se fait en deux temps : on accède à un ou plusieurs RAM ''direct-mapped'' et on vérifie ensuite les ''Tags'' pour sélectionner la bonne voie. Sur les caches ''direct-mapped'', on n'a qu'une seule RAM ''direct-mapped''. Sur les caches associatifs, on a plusieurs RAM ''direct-mapped'', appelées des voies, qui sont accédées en parallèle. L'accès se fait donc en deux étapes : adresser les RAM ''direct-mapped'' avec un indice, vérifier les ''tags'' avec le reste de l'adresse.
Une autre chose à rappeler est que l'adresse logique est composée de deux parties : un numéro de page logique qui indique dans quel page se situe l'adresse, un décalage/''offset'' qui indique la position de l'adresse dans la page. La traduction d'adresse transforme le numéro de page logique en numéro de page physique, mais laisse le décalage intouché. L'idée est d'utiliser le décalage pour adresser les RAM avec le décalage, tandis que le numéro de page sert de ''tag''. Le décalage est découpé en deux lors de l'accès au cache : les bits de poids fort forment l'indice (l'adresse envoyée à la voie), les bits de poids faible donnent la position de l'adresse dans la ligne de cache.
L'idée est d'utiliser un numéro de page physique pour les ''tags'', mais d'adresser les voies avec le décalage logique. Les deux servent à des instants différents : vérification des ''tags'' pour l'adresse physique, accès aux voies pour l'adresse logique. Ainsi, le problème des adresses synonymes ou homonymes est résolu par l'utilisation de l'adresse physique pour les tags. Par contre, l'accès au cache est plus rapide, car on utilise l'adresse logique pour la première étape. Le processeur accède à la TLB et récupère l'adresse physique pendant que l'on adresse les voies, les deux sont faits en parallèle, ce qui fait que tout se passe comme si l'accès à la TLB était gratuit. La TLB étant assez rapide comparé au cache, l'adresse physique est disponible quand on doit faire la comparaison avec les ''tags''.
[[File:Virtual - Physical - Pseudo Virtual addressing.svg|centre|vignette|upright=2|Adressage pseudo virtuel des caches.]]
Il s'agit d'un excellent compromis entre performance et correction des problèmes des adresses synonymes/homonymes. Tous les caches des processeurs haute performance utilisent cette méthode, au moins pour leurs caches L1. Les caches L2 tendent à utiliser des caches physiquement adressés, pour lesquels la latence d'accès est suffisante pour qu'on accède à la TLB en amont. La raison est assez simple à expliquer, elle provient d'une contrainte assez précise sur le calcul de l'indice.
La conséquence est qu'un cache ''direct-mapped'' ne peut pas dépasser la taille d'une page, soit 4 kibioctets sur les ordinateurs actuels. Sur les caches associatifs, on peut dépasser cette limite en augmentant le nombre de voies, mais la taille maximale d'une voie reste celle d'une page. Cette contrainte n'est pas trop grave sur les caches de petite taille, dont les caches L1. La plupart d'entre eux ont trouvé un compromis idéal avec moins d'une dizaine de voies par cache, chacun de 4 kibioctets, ce qui donne des caches allant de 16 à 64 kibioctets, soit entre 4 et 16 voies. Par contre, un cache de grande taille doit utiliser un grand nombre de voies, ce qui est peu pratique. Aussi, cette technique de caches pseudo-virtuels n'est pas toujours appliquée sur les caches L2, qui sont physiquement adressés. Il faut dire qu'on accède au cache L2 lors d'un défaut dans le cache L1, et l'adresse physique est disponible à ce moment-là, elle a déjà été récupérée lors de l'accès au cache L1. On peut donc l'utiliser pour adresser le cache L2 sans perte de performance.
==Le remplacement des lignes de cache==
Lorsqu'un cache est rempli et qu'on charge une nouvelle donnée dedans, il faut faire de la place pour cette dernière. Dans le cas d'un cache directement adressé, il n'y a rien à faire vu que la ligne de cache à évincer est déterminée lors de la conception du cache. Mais pour les autres caches, la donnée peut aller dans n'importe quelle ligne ou voie. Or, le choix des données à rapatrier en RAM doit être le plus judicieux possible : on doit virer de préférence des données inutiles. Rapatrier une donnée qui sera surement utilisée sous peu est inutile, et il vaudrait mieux supprimer des données qui ne serviront plus ou alors dans longtemps.
Il existe différents algorithmes spécialement dédiés à résoudre ce problème efficacement, directement câblés dans les unités de gestion du cache. Certains sont vraiment très complexes, aussi je vais vous présenter quelques algorithmes particulièrement simples.
Mais avant de voir ces algorithmes, il faut absolument que je vous parle d'une chose très importante. Quel que soit l'algorithme en question, il choisit la ligne de cache à évincer et recopie son contenu dans la RAM. Ce qui demande d'identifier et de sélectionner une ligne de cache parmi toutes les autres. Pour cela, le circuit de remplacement attribue une adresse chaque ligne de cache ! Vous avez bien vu : chaque ligne de cache est numérotée par une adresse, interne au cache.
===Le remplacement aléatoire===
Premier algorithme : la donnée effacée du cache est choisie au hasard ! C'est contre-intuitif, mais cet algorithme donne des résultats assez honorables, en plus d'utiliser très peu de portes logiques (un générateur de nombres pseudo-aléatoire est un circuit assez simple). Généralement, les défauts de cache sont séparés par un nombre assez important et irrégulier de cycles d'horloge. Dans ces conditions, cette technique donne un bon résultat.
===FIFO : first in, first out===
Avec l'algorithme FIFO, la donnée effacée du cache est la plus ancienne, celle chargée dans le cache avant les autres. Cet algorithme est très simple à implémenter en circuit, concevoir une mémoire de type FIFO n'étant pas très compliqué, comme on l’a vu dans le chapitre dédié à ce type de mémoires. Et on peut dire que dans le cas d'un cache, l'implémentation est encore plus simple et se contente d'un seul registre/compteur. Typiquement, il suffit d'ajouter un registre qui mémorise où se situe la donnée la plus récente. Toute insertion d'une nouvelle donnée se fait à l'adresse suivante, ce qui demande juste d'incrémenter le registre avant d'utiliser son contenu pour l'accès mémoire.
[[File:Algorithme FIFO de remplacement des lignes de cache.png|centre|vignette|upright=2|Algorithme FIFO de remplacement des lignes de cache.]]
Cet algorithme possède une petite particularité sur les caches associatifs par voie : en augmentant le nombre d'ensembles, les performances peuvent se dégrader : c'est ce qu'on appelle l''''anomalie de Bélády'''.
===MRU : most recently used===
Avec l'algorithme MRU, la donnée remplacée est celle qui a été utilisée le plus récemment. Cet algorithme s'implémente simplement avec un registre, dans lequel on place le numéro de la dernière ligne de cache utilisée.
Cet algorithme de remplacement est très utile quand un programme traverse des tableaux du premier élément jusqu'au dernier : les données du tableau sont rarement réutilisées, rendant le cache inutile. Il est prouvé que dans ces conditions, l'algorithme MRU est optimal. Mais dans toutes les autres conditions, cet algorithme a des performances assez misérables.
===LFU : least frequently used===
Avec l'algorithme LFU, la donnée supprimée est celle qui est utilisée le moins fréquemment. Cet algorithme s'implémente en associant un compteur à chaque ligne de cache, qui est incrémenté à chaque accès mémoire. La ligne la moins récemment utilisée est celle dont le compteur associé a la plus petite valeur. Implémenter cet algorithme prend pas mal de transistors, car il faut rajouter autant de compteurs qu'il y a de lignes de cache, en plus d'un circuit pour comparer les compteurs et d'un encodeur.
[[File:Algorithme LFU de remplacement des lignes de cache.png|centre|vignette|upright=2|Algorithme LFU de remplacement des lignes de cache]]
===LRU : least recently used===
Avec l'algorithme LRU, la donnée remplacée est celle qui a été utilisée le moins récemment. Cet algorithme se base sur le principe de localité temporelle, qui stipule qu'une donnée accédée récemment a de fortes chances d'être réutilisée dans un futur proche. Et inversement, la donnée la moins récemment utilisée du cache est celle qui a le plus de chance de ne servir à rien dans le futur. Autant la supprimer en priorité pour faire de la place à des données potentiellement utiles.
Implémenter l'algorithme LRU peut se faire de différentes manières, qui ont pour point commun d'enregistrer les accès au cache pour en déduire la ligne la moins récemment accédée. La manière la plus simple demande d'utiliser un compteur pour chaque ligne de mémoire cache, un peu comme le LFU. La différence avec le LFU est que le compteur n'est pas incrémenté lors d'un accès mémoire. À la place, ce compteur est incrémenté régulièrement, chaque incrémentation ayant lieu en même temps pour tous les compteurs. Quand un bloc est chargé dans le cache, ce compteur est mis à zéro. Quand une ligne de cache doit être remplacée, un circuit va vérifier la valeur de tous les compteurs : la ligne LRU (la moins récemment utilisée), est celle dont le compteur a la valeur la plus haute. Le circuit est composé d'un paquet de comparateurs, et d'un encodeur, comme pour l'agorithme LFU.
===Les approximations du LRU===
Implémenter le LRU demande un nombre de transistors proportionnel au carré du nombre de lignes de cache. Autant dire que le LRU devient impraticable sur de gros caches. Ce qui fait que les processeurs modernes implémentent des variantes du LRU, moins couteuses en transistors, qui donnent un résultat approximativement semblable au LRU. En clair, ils ne sélectionnent pas toujours la ligne de cache la moins récemment utilisée, mais une ligne de cache parmi les moins récemment utilisées. Ce n'est pas un problème si grave que cela car les lignes les moins récemment utilisées ont toutes assez peu de chance d'être utilisées dans le futur. Entre choisir de remplacer une ligne qui a 0,5 % de chances d'être utilisée dans le futur et une autre qui a une chance de seulement 1 %, la différence est négligeable en termes de taux de succès. Mais les gains en termes de circuits ou de temps d'accès au cache de ces algorithmes sont très intéressants.
L'algorithme le plus simple consiste à couper le cache (ou chaque voie s'il est associatif) en plusieurs sections. L'algorithme détermine la section la moins récemment utilisée, avant de choisir aléatoirement une ligne de cache dans cette section. Pour implémenter cet algorithme, il nous suffit d'un registre qui mémorise le morceau le moins récemment utilisé, et d'un circuit qui choisit aléatoirement une ligne de cache. Cette technique s'adapte particulièrement bien avec des caches associatifs à voies : il suffit d'utiliser autant de morceaux que de voies.
Autre algorithme, un peu plus efficace : le '''pseudo-LRU de type M'''. Cet algorithme attribue un bit à chaque ligne de cache, bit qui sert à indiquer de façon approximative si la ligne de cache associée est une candidate pour un remplacement ou non. Il vaut 1 si la ligne n'est pas une candidate pour un remplacement et zéro sinon. Le bit est mis à 1 lorsque la ligne de cache associée est lue ou écrite. Évidemment, au fil du temps, toutes les lignes du cache finiront par avoir leur bit à 1. Lorsque cela arrive, l'algorithme remet tous les bits à zéro, sauf pour la dernière ligne de cache accédée. L'idée derrière cet algorithme est d'encercler la ligne de cache la moins récemment utilisée au fur et à mesure des accès. L'encerclement commence lorsque l'on remet tous les bits associés aux lignes de cache à 0, sauf pour la ligne accédée en dernier. Au fur et à mesure des accès, l'étau se resserre autour de la ligne de cache la moins récemment utilisée. Après un nombre suffisant d'accès, l'algorithme donne une estimation particulièrement fiable. Et comme les remplacements de lignes de cache sont rares comparés aux accès aux lignes, cet algorithme finit par donner une bonne estimation avant qu'on ait besoin d'effectuer un remplacement.
Le dernier algorithme d'approximation, le '''PLURt''', se base sur ce qu'on appelle un arbre de décision. Il a besoin de n − 1 bits pour déterminer la ligne LRU. Ces bits doivent être organisés en arbre, comme illustré plus bas. Chacun de ces bits sert à dire : le LRU est à ma droite ou à ma gauche : il est à gauche si je vaux 0, et à droite si je vaux 1. Trouver le LRU se fait en traversant cet arbre, et en interprétant les bits un par un. Au fur et à mesure des lectures, les bits sont mis à jour dans cet arbre, et pointent plus ou moins bien sur le LRU. La mise à jour des bits s'effectue lors des lectures et écritures : quand une ligne est lue ou écrite, elle n'est pas la ligne LRU. Pour l'indiquer, les bits à 1 qui pointent vers la ligne de cache sont mis à 0 lors de la lecture ou écriture.
{|
|[[File:Organisation des bits avec l'algorithme PLURt.jpg|vignette|Organisation des bits avec l'algorithme PLURt.]]
|[[File:Ligne de cache pointée par les bits de l'algorithme.png|vignette|Ligne de cache pointée par les bits de l'algorithme.]]
|}
===LRU amélioré===
L'algorithme LRU, ainsi que ses variantes approximatives, sont très efficaces tant que le programme respecte relativement bien la localité temporelle. Par contre, Le LRU se comporte assez mal dans les circonstances ou la localité temporelle est mauvaise mais où la localité spatiale est respectée, le cas le plus emblématique étant le parcours d'un tableau. Pour résoudre ce problème, des variantes du LRU existent.
Une variante très connue, l''''algorithme 2Q''', utilise deux caches : un cache FIFO pour les données accédées une seule fois et un second cache LRU. Évidemment, les données lues une seconde fois sont migrées du cache FIFO vers le cache LRU, ce qui n'est pas très pratique. Les processeurs n'utilisent donc pas cette technique, mais celle-ci est utilisée dans les caches de disque dur.
D'autres variantes du LRU combinent plusieurs algorithmes à la fois et vont choisir lequel de ces algorithmes est le plus adapté à la situation. Notre cache pourra ainsi détecter s’il vaut mieux utiliser du MRU, du LRU, ou du LFU suivant la situation.
==Les écritures dans le cache : gestion et optimisations==
Les écritures se font à une adresse mémoire bien précise, qui peut ou non être chargée dans le cache. Si la donnée à écrire est chargée dans le cache, elle est modifiée directement dans le cache, mais elle ne l'est pas forcément en mémoire RAM. Suivant le processeur, les écritures sont ou non propagées en mémoire RAM. Il existe deux stratégies d'écritures, appelées respectivement le ''write-back'' et le ''write-through''.
Avec un cache ''write-back'', si la donnée à mettre à jour est présente dans le cache, on écrit dans celui-ci sans écrire dans la mémoire RAM. Dans ces conditions, une donnée n'est enregistrée en mémoire que si celle-ci quitte le cache, ce qui évite de nombreuses écritures mémoires inutiles.
[[File:Cache write-through.png|centre|vignette|upright=2|Cache write-through.]]
Avec les caches '''Write-Through''', toute écriture dans le cache est propagée en RAM. Cette stratégie augmente le nombre d'écritures dans la mémoire RAM, ce qui peut saturer le bus reliant le processeur à la mémoire. Les performances de ces caches sont donc légèrement moins bonnes que pour les caches ''write back''. Par contre, ils sont utiles dans les architectures avec plusieurs processeurs, comme nous le verrons dans les chapitres sur les architectures multiprocesseurs.
[[File:Cache write-back.png|centre|vignette|upright=2|Cache write-back.]]
===Les caches ''Write-through''===
Sans optimisation particulière, on ne peut écrire dans un cache ''write-through'' pendant qu'une écriture en RAM a lieu en même temps : cela forcerait à effectuer deux écritures simultanées, en comptant celle imposée par l'écriture dans le cache.
Pour éviter cela, certains caches ''write-through'' intègrent un '''tampon d’écriture''', qui sert de file d'attente pour les écritures en RAM. C'est une mémoire FIFO dans laquelle on place temporairement les données à écrire en RAM, où elles attendent en attendant que la RAM soit libre. Grâce à lui, le processeur peut écrire dans un cache même si d'autres écritures sont en attente dans le tampon d'écriture. Par souci d'efficacité, des écritures à la même adresse en attente dans le tampon d’écriture sont fusionnées en une seule. Cela fait un peu de place dans le tampon d’écriture, et lui permet d'accumuler plus d'écritures avant de devoir bloquer le cache. Il est aussi possible de fusionner des écritures à adresses consécutives de la mémoire en une seule écriture en rafales. Dans les deux cas, on parle de '''combinaison d'écriture'''.
Mais la technique du tampon d'écriture a cependant un léger défaut qui se manifeste dans une situation bien précise : quand le processeur veut lire une donnée en attente dans le tampon d’écriture. La première manière de gérer cette situation est de mettre en attente la lecture tant que la donnée n'a pas été écrite en mémoire RAM. On peut aussi lire la donnée directement dans le tampon d'écriture, cette optimisation portant le nom de '''''store-to-load forwading'''''. Dans tous les cas, il faut détecter le cas où une lecture accède à une donnée dans le tampon d'écriture. À chaque lecture, l'adresse à lire est envoyée au tampon d'écriture, qui vérifie si une écriture en attente se fait à cette adresse. Pour cela, le tampon d’écriture doit être un cache, dont chaque entrée mémorise une écriture. Chaque ligne de cache contient la donnée à écrire, et le tag de la ligne de cache contient l'adresse où écrire la donnée. Notons que cache d'écriture a une politique de remplacement de type FIFO, le tampon d'écriture non-optimisé étant une mémoire FIFO.
===Les caches ''Write-back''===
Les caches ''write-back'' ont beau avoir des performances supérieures à celles des caches ''write-through'', il existe des optimisations qui permettent d'améliorer leurs performances. Ces optimisations consistent à ajouter des caches spécialisés à côté du cache proprement dit. Ces caches permettent de mémoriser des données qui sont éliminées du cache par les algorithmes de remplacement de ligne cache, sans pour autant faire une écriture en RAM.
En suivant la procédure habituelle de remplacement des lignes de cache, on doit rapatrier la ligne en RAM avant d'en charger une nouvelle. On peut améliorer la situation en faisant l'inverse : on charge la nouvelle ligne pendant que l'ancienne donnée est rapatriée en RAM. Ainsi, la nouvelle ligne est disponible plus tôt pour le processeur, diminuant son temps d'attente. Pour implémenter cette technique, on doit mémoriser l'ancienne ligne de cache temporairement dans un '''cache d’éviction''' (ou ''write-back buffer'').
[[File:Cache d’éviction.png|centre|vignette|upright=2|Cache d’éviction]]
Les caches directement adressés ou associatifs par voie possèdent aussi un tampon d’écriture amélioré. Pour limiter les défauts par conflit de ces caches, des scientifiques ont eu l'idée d'insérer un cache pour stocker les données virées du cache. En faisant ainsi, si une donnée est virée du cache, on peut alors la retrouver dans ce cache spécialisé. Ce cache s'appelle le '''cache de victime'''. Ce cache de victime est géré par un algorithme de suppression des lignes de cache de type FIFO. Petit détail : ce cache utilise un tag légèrement plus long que celui du cache directement adressé au-dessus de lui. L'index de la ligne de cache doit en effet être contenu dans le tag du cache de victime, pour bien distinguer deux adresses différentes, qui iraient dans la même ligne du cache juste au-dessus.
[[File:Victim Cache Implementation Example.svg|centre|vignette|upright=1|Cache de victime.]]
===La configuration du fonctionnement du cache===
Sur de nombreux processeurs, il est possible de configurer la mémoire cache pour qu'elle fonctionne soit en mode ''write-back'', soit en mode ''write-through''. Pour cela, les processeurs modernes incorporent des '''registres de configuration du cache'''. Le terme ''registre de configuration du cache'' est assez transparent et indique bien quel est leur rôle. Ils configurent comment le cache est utilisé et permettent notamment de configurer le cache pour dire s'il doit fonctionner en mode ''write-back'' ou ''write-through''. Ils permettent aussi d'activer ou de désactiver la combinaison sur écriture.
Les registres en question sont configurés soit par le BIOS, soit par le système d'exploitation. Ce sont des registres protégés, que les applications ne peuvent pas configurer, elles n'en ont pas le droit. Typiquement, ils ne sont accessibles en écriture qu'en mode noyau.
Sur les processeurs x86, les registres de configuration du cache sont appelés des '''''Memory type range registers''''' (''MTRRs''). Les MTRRs sont assez nombreux, et il y a notamment une différence entre mode réel et protégé. Si vous vous souvenez des chapitres sur le mode d'adressage et la mémoire virtuelle, vous vous souvenez que les processeurs x86 incorporent plusieurs modes de fonctionnement. En mode réel, le processeur ne peut adresser qu'un mébioctet de RAM, avec un système de segmentation particulier. En mode protégé, le processeur peut adresser toute la mémoire et la segmentation fonctionne différemment, quand elle n'est pas simplement désactivée.
Les MTRRs sont séparés en deux : ceux pour le mode réel, ceux pour le mode protégé. Les MTRRs fixes sont ceux qui configurent le cache en mode réel, ils étaient utilisés pour gérer l'accès au BIOS, à la mémoire VGA de la carte graphique, et quelques autres accès aux entrées-sorties basiques gérées nativement par le BIOS. Pour le mode protégé, les processeurs au-delà du 386 incorporent des MTRRs variables, qui servent pour les autres entrées-sorties en général, notamment les périphériques PCI, la mémoire vidéo de la carte graphique, et j'en passe.
De nos jours, les registres de configuration du cache sont désuets et cette fonctionnalité est gérée directement par la mémoire virtuelle. La table des pages contient, pour chaque page mémoire, des bits de contrôle qui disent si la page mémoire est cacheable ou non. Le contournement de cache est alors géré par le système de mémoire virtuelle, le cache de TLB et tout ce qui va avec.
===L’allocation sur écriture===
Que faire quand une écriture modifie une donnée qui n'est pas dans le cache ? Doit-on écrire la donnée dans le cache, ou non ? Si la donnée est écrite dans le cache, on dit que le cache fait une '''allocation sur l'écriture''' (ou ''write-allocate''). Certains caches effectuent une telle allocation sur écriture, mais d'autres ne le font pas ou du moins pas systématiquement.
L’allocation sur écriture peut se décliner en deux sous-catégories : le '''chargement à la demande''' et l''''écriture immédiate'''. Dans le premier cas, on charge la donnée à modifier dans le cache, et on la remplace avec la donnée écrite. Dans l'écriture immédiate, l'écriture a lieu directement dans le cache et la donnée à modifier n'est pas chargée dans le cache. Évidemment, seule une portion de la ligne de cache contient la donnée écrite (valide), et le reste contient des données invalides. Le cache doit savoir quelles sont les portions du cache qui sont valides : cela demande d'utiliser un ''sector cache''.
[[File:Write-back with write-allocation.svg|centre|vignette|upright=2|Cache Write-back avec allocation sur écriture.]]
Sans allocation sur écriture, l'écriture est transférée directement aux niveaux de cache inférieurs ou à la mémoire si la donnée à modifier n'est pas dans le cache. Certains caches de ce genre utilisent une petite optimisation : lors de toute écriture, ils supposent que l'écriture donnera un succès de cache. Si c'est le cas, la ligne de cache qui contient la donnée est mise à jour avec la donnée à écrire. Mais si ce n'est pas le cas, la ligne de cache est invalidée, et l'écriture est transférée directement à la mémoire ou aux niveaux de cache inférieurs.
[[File:Write-through with no-write-allocation.svg|centre|vignette|upright=2|Cache Write-through sans allocation sur écriture.]]
===La cohérence des caches===
Il arrive parfois que la mémoire d'un ordinateur soit mise à jour, sans que les modifications soient répercutées dans les mémoires cache. Dans ce cas, le cache contient une donnée périmée. Or, un processeur doit toujours éviter de se retrouver avec une donnée périmée et doit toujours avoir la valeur correcte dans ses caches : cela s'appelle la '''cohérence des caches'''. Il est possible de se retrouver avec des valeurs périmées dans le cache sur les ordinateurs avec plusieurs processeurs, ou si un périphérique écrit en RAM, les modifications ne sont pas répercutées automatiquement dans les mémoires cache.
Pour résoudre ce problème, on peut interdire de charger dans le cache des données stockées dans les zones de la mémoire dédiées aux périphériques. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires cache. Autre solution : utiliser le fait que les périphériques déclenchent une interruption matérielle pour laisser le contrôleur DMA accéder à la mémoire. Dans ce cas, il suffit de vider les caches à chaque interruption matérielle. Le processeur peut le faire automatiquement, ou fournir des instructions pour.
==Le ''cache bypassing'' : contourner le cache==
Dans certaines situations, le cache n'est pas utilisé pour certains accès mémoire. Diverses techniques permettent en effet d'effectuer des accès mémoire qui contournent le cache, qui ne passent pas par le cache. Ils sont utilisés quand l'accès en cache fait que des instructions normales ne fonctionnent pas. Par exemple, de tels accès directs à la RAM sont notamment utilisés pour l'implémentation d'instructions atomiques, une classe d'instructions spécifiques utilisées sur les processeurs multicœurs, dont nous parlerons dans plusieurs chapitres. Mais ils sont aussi utilisés pour l'accès aux périphériques, ce que nous allons voir maintenant.
===Accéder aux périphériques demande de contourner le cache===
Pour rappel, un périphérique (au sens d'entrée-sortie) contient des registres d’interfaçage qui ont une adresse au même titre que les cases mémoire. Un périphérique peut à tout instant modifier ses registres d’interfaçage, ce qui se répercute automatiquement dans l'espace d'adressage, mais rien de tout cela n'est transmis au cache. Si les accès aux périphériques passaient par l'intermédiaire du cache, on aurait droit à des problèmes. On aurait encore une fois droit à des problèmes de cohérence des caches. Le problème est géré différemment suivant que l'on utilise un espace d'adressage séparé ou des entrées-sorties mappées en mémoire.
La solution est que les accès aux périphériques ne doivent pas passer par l’intermédiaire du cache. Cela demande d'adapter le cache et le processeur. L'implémentation exacte dépend de comment sont adressés les périphériques. Pour rappel, il y a deux solutions pour adresser les périphériques : soit les périphériques disposent d'un espace d'adressage séparé de celui de la mémoire, soit il y un espace d'adressage unique partagé entre processeur et mémoire. Les deux cas donnent des solutions différentes.
Avec un espace d'adressage séparé, l'espace d'adressage des périphériques n'est pas caché : aucun accès dans cet espace d'adressage ne passe par le cache. La mémoire cache n'est utilisée que pour l'espace d'adressage des mémoires, rien d'autre. C'est de loin le cas le plus simple : il suffit de concevoir le processeur pour. Il dispose d'instructions séparées pour les accès aux registres d’interfaçage et à la RAM/ROM, les premières ne passent pas par le cache, les autres si.
Avec des entrées-sorties mappées en mémoire, la même solution est utilisée, mais dans une version un peu différente. Là encore, les accès aux périphériques ne doivent pas passer par l’intermédiaire du cache, si on veut qu'ils marchent comme ils le doivent. Cela demande d'adapter le cache et le matériel pour que accès aux périphériques mappés en mémoire contournent le cache. Des adresses, voire des zones entières de la mémoire, sont marquées comme étant non-cachables. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. Là encore, le processeur doit être prévu pour : on doit pouvoir le configurer de manière à marquer certaines zones de la RAM comme non-cacheable.
Reste qu'il faut marquer des régions de la RAM comme non-cacheable. Pour cela, on améliore les registres de configuration du cache, vus plus haut, afin qu'ils permettent de configurer certaines portions de la RAM pour préciser qu'elles ne doivent pas être mises en cache, qu'il faut activer le contournement de cache pour celles-ci.
===Contourner le cache pour des raisons de performance===
Il arrive que des données avec une faible localité soient chargées dans le cache inutilement. Or, il vaut mieux que ces données transitent directement entre le processeur et la mémoire, sans passer par l'intermédiaire du cache. Pour cela, le processeur peut fournir des instructions d'accès mémoire qui ne passent pas par le cache, à côté d'instructions normales. De telle instructions sont appelées des '''instructions mémoire non-temporelles'''. Non-temporelle, dans le sens : pas de localité temporelle (c.a.d que les données ne seront pas réutilisées plus tard).
Mais il existe aussi des techniques matérielles, où le cache détecte à l'exécution les lectures qui gagnent à contourner le cache. La dernière méthode demande d'identifier les instructions à l'origine des défauts de cache, le processeur accédant directement à la RAM quand une telle instruction est détectée. Si une instruction d'accès mémoire fait trop de défauts de cache, c'est signe qu'elle gagne à contourner le cache. L'idée est de mémoriser, pour chaque instruction d'accès mémoire, un historique de ses défauts de cache. Il existe plusieurs méthodes pour cela, mais toutes demandent d'ajouter de quoi mémoriser l'historique des défauts de cache des instructions. L'historique est mémorisé dans une mémoire appelée la '''table d’historique des défauts de lecture''' (''load miss history table''), qui est souvent un cache.
L'historique en question est, dans sa version la plus simple, un compteur de quelques bits incrémenté à chaque succès de cache et décrémenté à chaque défaut de cache, qui indique si l'instruction a en moyenne fait plus de défauts ou de succès de cache. La table associe le ''program counter'' d'une instruction mémoire à cet historique. À la première exécution d'une instruction d'accès mémoire, une entrée de cette table est réservée pour l'instruction. Lors des accès ultérieurs, le processeur récupérer les informations associées et décide s'il faut contourner le cache ou non.
==La hiérarchie mémoire des caches==
[[File:Cache Hierarchy.png|vignette|Hiérarchie de caches]]
On pourrait croire qu'un seul cache est largement suffisant pour compenser la lenteur de la mémoire. Hélas, les processeurs sont devenus tellement rapides que les caches sont eux-mêmes très lents ! Pour rappel, plus une mémoire peut contenir de données, plus elle est lente. Et les caches ne sont pas épargnés. Si on devait utiliser un seul cache, celui-ci serait très gros et donc trop lent. La situation qu'on cherche à éviter avec la mémoire RAM revient de plus belle.
Même problème, même solution : si on a décidé de diviser la mémoire principale en plusieurs mémoires de taille et de vitesse différentes, on peut bien faire la même chose avec la mémoire cache. Depuis environ une vingtaine d'années, un processeur contient plusieurs caches de capacités très différentes : les caches L1, L2 et parfois un cache L3. Certains de ces caches sont petits, mais très rapides : c'est ceux auxquels on va accéder en priorité. Viennent ensuite d'autres caches, de taille variable, mais plus lents. Les processeurs ont donc une hiérarchie de caches qui se fait de plus en plus complexe avec le temps. Cette hiérarchie est composée de plusieurs niveaux de cache, qui vont des niveaux inférieurs proches de la mémoire RAM à des niveaux supérieurs proches du processeur. Plus on monte vers les niveaux supérieurs, plus les caches sont petits et rapides.
Un accès mémoire dans une hiérarchie de cache fonctionne comme suit : on commence par vérifier si la donnée recherchée est dans le cache le plus rapide, à savoir le cache L1. Si c'est le cas,n on la charge depuis ce cache directement. Si elle n’y est pas, on vérifie si elle est dans le cache de niveau supérieur, le cache L2. Et rebelote ! Si elle n'y est pas, on vérifie le cache du niveau supérieur. Et on répète cette opération, jusqu’à avoir vérifié tous les caches. Si la donnée n'est dans aucun cache, on doit alors aller chercher la donnée en mémoire.
[[File:Hiérarchie de caches.png|centre|vignette|upright=2|Hiérarchie de caches]]
Il y a des différences assez notables entre chaque niveau de cache. Par exemple, les différents niveaux de cache n'ont pas forcément les mêmes politiques de remplacement des lignes de cache. Le cache L1 a généralement une politique de remplacement simple, très rapide, mais peu efficace. De même, il faut aussi savoir que la taille des lignes de cache n'est pas la même suivant les niveaux de cache. Par exemple, le L2 peut avoir des lignes plus grandes que celles du L1.
Le cache le plus proche de la mémoire est appelé le '''cache de dernier niveau''', ''Last Level Cache'' en anglais. Il a parfois des caractéristiques totalement différentes des autres caches. Par exemple, sur les processeurs multicoeurs, le cache L3 n'est techniquement pas dans le cœur, mais fait partie d'un ensemble de circuits reliés, comme le contrôleur mémoire ou l'interface mémoire. Il fonctionne à une fréquence différente du processeur, n'a pas la même tension d'alimentation, etc.
===Les caches exclusifs et inclusifs===
Notons que du point de vue de cette vérification, il faut distinguer les caches inclusifs et exclusifs. Avec les caches inclusifs, si une donnée est présente dans un cache, alors elle est présente dans les caches des niveaux inférieurs, ce qui implique l'existence de données en doublon dans plusieurs niveaux de cache. À l'opposé, les caches exclusifs font que toute donnée est présente dans un seul cache, pas les autres. Il existe aussi des caches qui ne sont ni inclusifs, ni exclusifs. Sur ces caches, chaque niveau de cache gère lui-même ses données, sans se préoccuper du contenu des autres caches. Pas besoin de mettre à jour les niveaux de cache antérieurs en cas de mise à jour de son contenu, ou en cas d'éviction d'une ligne de cache. La conception de tels caches est bien plus simple.
Dans les '''caches exclusifs''', le contenu d'un cache n'est pas recopié dans le cache de niveau inférieur. Il n'y a pas de donnée en double et on utilise 100 % de la capacité du cache, ce qui améliore le taux de succès. Par contre, le temps d'accès est un peu plus long. La raison est que si une donnée n'est pas dans le cache L1, on doit vérifier l'intégralité du cache L2, puis du cache L3. De plus, assurer qu'une donnée n'est présente que dans un seul cache nécessite aux différents niveaux de caches de communiquer entre eux pour garantir que l'on a pas de copies en trop d'une ligne de cache, ce qui peut prendre du temps.
[[File:Caches exclusifs.png|centre|vignette|upright=2|Caches exclusifs]]
Dans le cas des '''caches inclusifs''', le contenu d'un cache est recopié dans les caches de niveau inférieur. Par exemple, le cache L1 est recopié dans le cache L2 et éventuellement dans le cache L3. Ce genre de cache a un avantage : le temps d'accès à une donnée est plus faible. La raison est qu'il ne faut pas vérifier tout un cache, mais seulement la partie qui ne contient pas de donnée en doublon. Par exemple, si la donnée voulue n'est pas dans le cache L1, on n'est pas obligé de vérifier la partie du cache L2 qui contient la copie du L1. Ainsi, pas besoin de vérifier certaines portions du cache, ce qui est plus rapide et permet de simplifier les circuits de vérification. En contrepartie, l'inclusion fait que qu'une partie du cache contient des copies inutiles, comme si le cache était plus petit. De plus, maintenir l'inclusion est compliqué et demande des circuits en plus et/ou des échanges de données entre caches.
[[File:Caches inclusifs.png|centre|vignette|upright=2|Caches inclusifs]]
Maintenir l'inclusion demande de respecter des contraintes assez fortes, ce qui ne se fait pas facilement. Premièrement, toute donnée chargée dans un cache doit aussi l'être dans les caches de niveau inférieur. Ensuite, quand une donnée est présente dans un cache, elle doit être maintenue dans les niveaux de cache inférieurs. De plus, toute donnée effacée d'un cache doit être effacée des niveaux de cache supérieurs : si une donnée quitte le cache L2, elle doit être effacée du L1. Ces trois contraintes posent des problèmes si chaque cache décide du remplacement des lignes de cache en utilisant un algorithme comme LRU, LFU, MRU, ou autre, qui utilise l'historique des accès. En effet, dans ce cas, le cache décide de remplacer les lignes de cache selon l'historique des accès, historique qui varie suivant chaque niveau de cache. Par exemple, une donnée rarement utilisée dans le L2 peut parfaitement être très fréquemment utilisée dans le L1 : la donnée sera alors remplacée dans le L2, mais sera maintenue dans le L1. On observe aussi des problèmes quand il existe plusieurs caches à un seul niveau : chaque cache peut remplacer les lignes de cache d'une manière indépendante des autres caches du même niveau, donnant lieu au même type de problème.
Pour maintenir l'inclusion, les caches doivent se transmettre des informations qui permettent de maintenir l'inclusion. Par exemple, les caches de niveaux inférieurs doivent prévenir les niveaux de cache supérieurs quand ils remplacent une ligne de cache. De plus, toute mise à jour dans un cache doit être répercutée dans les niveaux de cache inférieurs et/ou supérieurs. On doit donc transférer des informations de mise à jour entre les différents niveaux de cache. Généralement, le contenu des caches d'instruction n'est pas inclus dans les caches de niveau inférieurs, afin d'éviter que les instructions et les données se marchent sur les pieds.
Enfin, il faut aussi savoir que la taille des lignes de cache n'est pas la même suivant les niveaux de cache. Par exemple, le L2 peut avoir des lignes plus grandes que celles du L1. Dans ce cas, l'inclusion est plus difficile à maintenir, pour des raisons assez techniques.
===Les caches eDRAM, sur la carte mère et autres===
D'ordinaire, les mémoires caches sont intégrées au processeur, à savoir que cache et CPU sont dans le même circuit imprimé. Les caches sont donc fabriqués avec de la SRAM, seule forme de mémoire qu'on peut implémenter dans un circuit intégré. Intégrer tous les caches dans le processeur est une solution et efficace. Mais certains processeurs ont procédé autrement.
[[File:Cache-on-a-stick module.jpg|vignette|Cache-on-a-stick module]]
Des processeurs assez anciens incorporaient un cache L1 dans le processeur, mais plaçaient un cache L2 sur la carte mère. Le cache était clippé sur un connecteur sur la carte mère, un peu comme le sont les barrettes de mémoire. On parlait alors de '''''Cache on a stick''''' (COAST). On aurait pu s'attendre à ce que de tels caches soient en DRAM, vu qu'ils sont placés sur des barrettes de RAM, mais la ressemblance avec la mémoire RAM principale s'arrête là. Le cache était fabriqué en mémoire SRAM, même s'il est en théorie possible de faire de tels caches avec de la DRAM.
Les premiers processeurs avec un cache faisaient ainsi, au début des années 90. Il a été introduiot sur les processeurs Motorola, et a été utilisé sur les IBM PC et les Macintosh de l'époque. Les ordinateurs Macintosh utilisaient de tels caches, pour la pluaprt des modèles. Pour ce qui est des PC, les premiers processeurs x86 faisaient pareil, notamment les processeurs Intel. Le 486, le Pentium et le Pentium 2 utilisaient des ''Cache on a stick''.
L'avantage est que cela permettait de mettre plus de cache, à une époque où les circuits étaient limités en transistors. De plus, cela permettait au consommateur de choisir quelle quantité de cache il voulait, selon ses finances. Il était possible de laisser le processeur fonctionner soit sans mémoire cache, soit avec un cache de 256 Kibioctets, de 512 Kibioctets, etc. Il était possible d'upgrader le cache si besoin.
Pour les CPU Intel, le cache était connecté sur le bus système, au même titre que la mémoire RAM et les entrées-sorties. Il faut dire que les processeurs de l'époque utilisaient un bus système et n'avaient pas de bus mémoire dédié. Mais en théorie, rien n’empêche de connecter le cache sur un bus mémoire dédié. Toujours est-il que les lectures et écritures étaient propagées à la fois dans le cache et la RAM. Les écritures se faisaient dans les deux, systématiquement dans la RAM, mais aussi dans le cache en cas de succès de cache. Les lectures étaient servies soit par le cache en cas de succès de cache, soit par la RAM en cas de défaut de cache. Si le cache répondait en premier, la transaction sur le bus se terminait précocement et l'accès en RAM était abandonné.
[[File:Intel486 Иерархия памяти.png|centre|vignette|upright=2.5|Intel486 : le cache était connecté sur le bus système.]]
À l'inverse, certains processeurs possédaient un cache fabriqué en mémoire DRAM, et plus précisément avec de la mémoire eDRAM. Le cache n'était pas intégré dans le même circuit imprimé que le processeur, mais profitait d'une architecture en ''chiplet''. Pour rappel, cela veut dire que le processeur est en réalité composé de plusieurs circuits intégré séparés, mais interconnectés et soudés sur un même PCB carré. Avec un cache en eDRAM, le cache avait son propre circuit intégré, séparé du circuit intégré du processeur ou du circuit intégré pour le contrôleur mémoire/IO. Un exemple est celui du cache des processeurs Intel de microarchitecture Broadwell, que nous allons voir dans la section suivante.
==Les caches splittés (''phased caches'')==
Dans cette section, nous allons voir les '''caches splittés''' (''phased caches''), dans lequel le cache est accédé en deux étapes consécutives. Il ne s'agit pas des caches pipelinés, que nous verrons dans le chapitre sur les processeurs pipélinés, mais laissons cela à plus tard. Nous allons partir du principe que ce sont des caches ''direct-mapped'', mais l'optimisation peut être utilisée sur un cache associatif par voie.
L'idée est de scinder le cache en deux : une mémoire pour les tags, une autre pour les données de la ligne de cache. Les bits de contrôle peuvent être mis dans l'une ou l'autre SRAM, mais ils sont souvent mis dans la RAM pour les tags. En faisant cela, quelques optimisations deviennent possibles, afin de réduire la consommation énergétique en contrepartie d'une perte de performance. La technique s'implémente différemment pour les caches totalement associatifs et partiellement associatifs.
Les caches totalement associatifs splittés sont ceux formés en combinant un cache associatif avec une CAM et une RAM combinée. On envoie l'adresse à lire/écrire à la mémoire associative, elle répond en envoyant une adresse à la mémoire RAM. L'accès se fait donc en deux temps, avec l'adresse dans la RAM comme intermédiaire. Il est possible de séparer physiquement les deux étapes en insérant un registre entre la CAM et la RAM, ce qui permet aussi de pipeliner l'accès. Mais c'est rarement fait en pratique, car le cout en circuit d'une mémoire CAM est trop important. L'équivalent pour un cache totalement associatif optimisé, sans CAM et RAM séparée, est trop gourmande en interconnexions pour être implémentée. Les caches totalement associatifs splittés sont donc très rares, l'auteur ne connait aucun exemple de processeur avec un tel cache.
Il existe une technique équivalente pour les caches ''direct-mapped'', mais elle demande une certaine modification du cache. Dans les caches ''direct-mapped'' non-splittés, on trouve une mémoire SRAM dont chaque mot mémoire contient une ligne de cache entière, tag inclus. Dans leurs versions splittés, la SRAM est séparée en deux : une pour les tags, une autre pour les données. Précisons qu'il s'agit bien de deux mémoires SRAM adressables. L'adresse à laquelle accéder est envoyée à la SRAM des tags, puis ensuite à la SRAM des données si besoin.
L'idée est d’accéder aux tags pour déterminer s'il y a un succès de cache ou un défaut, et ensuite d'accéder aux données. On n’accède pas aux données en parallèle des tags. Faire cela est évidemment plus lent. En cas de défaut de cache, le temps d'accès est similaire : le tag ne correspond pas, on n'accède pas à la SRAM pour les données. Par contre, vu qu'on n'a pas activé la SRAM pour les données, on économise un peu d'énergie, ce qui réduit la consommation d'énergie. En cas de succès de cache, on accède à la SRAM pour les tags, puis à celle pour les données. Pas d'économie d'énergie à l'horizon, sans compter que le temps d'accès augmente : on accède au cache en deux étapes au lieu de faire les deux accès en parallèle.
[[File:Phased cache.png|centre|vignette|upright=1.5|Phased cache]]
Précisons cependant que ce design peut avoir deux avantages en termes de performance. Premièrement, le temps d'accès au cache est légèrement amélioré en cas de défaut de cache. En effet, la SRAM des tags est assez petite, idem pour celle des données. Leur temps d'accès est donc plus faible que pour une grosse SRAM contenant données et tags. Le gain en temps d'accès est donc un avantage, qui ne se manifeste surtout en cas de défaut de cache. Un autre avantage est que l'accès au cache se pipeline plus facilement, ce qui fait qu'on peut effectuer plusieurs accès simultanés au cache. Mais nous verrons cela dans quelques chapitres.
===Le contrôleur de cache 82385 pour les CPU Intel 386===
Il est important de noter que la séparation entre tags et RAM peut être telle que les deux ne sont pas sur la même puce de silicium ! Voire que les deux sont séparés du processeur ! C'était le cas quand les mémoires caches ont été introduites sur les processeurs grand public, notamment sur les premiers processeurs Intel. La miniaturisation n'avait pas avancé au point où placer un cache dans le processeur était possible.
Sur le processeur 386 d'Intel, le cache était un cache splitté, séparé du processeur. Concrètement, le processeur i386 était couplé à un contrôleur de cache Intel 82385 et une mémoire SRAM. Le 82385 contenait les ''tags'' et les bits de contrôle, la SRAM contenait les données, les lignes de cache. Un point important est que les lignes de cache faisaient seulement 32 bits/4 octets, pas plus ! On était loin des lignes de cache actuelles, faisant 64 octets/512 bits. Mais c'était beaucoup plus pratique, vu que le bus système faisait 32 bits de large, idem pour l'interface avec le processeur.
Le schéma ci-dessous montre comment le cache s'intégrait avec le bus système. Pour le bus de commande, le cache servait d'intermédiaire : il recevait les commandes et les filtrait suivant les succès/défauts de cache. En cas de succès de cache, les commandes de lecture n'étaient pas envoyées à la mémoire RAM. Les adresses étaient transmises à la fois au cache et au bus système (avec un registre entre le bus système et le processeur). Le bus de donnée était lui connecté à la mémoire SRAM et au processeur, avec des MUX/DEMUX pour faire le choix de la source des lectures.
[[File:Controleur de cache 82385 pour l'Intel 386.png|centre|vignette|upright=2.5|Contrôleur de cache 82385 pour l'Intel 386]]
Le 82385 surveillait ce qui se passait sur le bus et répondait à la place de la RAM pour certaines lectures. C'était un intermédiaire assez passif, qui se contenait de répondre aux succès et défauts en lecture. Le cache était un cache ''write through'' un peu particulier. En cas de succès de cache pour une écriture, le cache met à jour sa ligne de cache et propage l'écriture en mémoire RAM. Par contre, si une écriture fait un défaut de cache, la donnée n'est pas écrite dans le cache. Le seul moyen pour copier une donnée dans le cache était un défaut pour une lecture.
Le 82385 pouvait commander soit un cache ''direct mapped'', soit associatif à deux voies. Le choix entre les deux était le fait d'une entrée : la mettre à 0 indiquait un cache ''direct mapped'', la mettre à 1 forçait un cache à deux voies. La différence entre les deux est que le 82385 était relié à une mémoire SRAM avec un cache ''direct mapped'', deux SRAM pour deux voies. Pour avoir un cache associatif à deux voies, le 82385 devrait gérer deux signaux ''chip select'' pour activer chaque SRAM/voie suivant les besoins. Il avait précisément quatre signaux CS : deux par SRAM, un pour les lectures, un pour les écritures. Notons que les lignes de cache faisaient 32 bits, ce qui pouvait d'obtenir soit avec une SRAM 32 bits, soit avec deux SRAM 16 bits, soit avec 4 SRAM 8 bits. Le 82385 rajoutait 4 sorties, pour masquer chaque octet dans ces 32 bits, qui sont techniquement des signaux ''Output Enable'' pour 4 SRAM 8 bits.
[[File:Interface entre le 82385 et la SRAM du cache.png|centre|vignette|upright=2|Interface entre le 82385 et la SRAM du cache. Beaucoup d'entrées et de sorties liées au bus d'adresse ne sont pas représentées.]]
Il gérait aussi les accès mémoire non-cacheable, à savoir des accès mémoire qui ne doivent pas être pris en compte par le cache. Il considérait certains accès mémoire comme "à ne pas cacher". Notamment, les accès mémoire à une entrée-sortie ne sont pas cachés. Pour rappel, le processeur utilisait un espace d'adressage séparé pour les entrées-sorties, et utilisait donc un bit IO, qui était utilisé par le 82385 pour savoir si l'accès mémoire doit être caché ou non. Il en est de même pour les accès ayant lieu lors d'une interruption, qui ne passent pas par le cache.
Mais au-delà de cette inhibition automatique du cache, le 82385 avait une entrée NCA (''Non Cacheable Access'') : le cache était "désactivé" quand cette entrée était à 1. C'est un peu une sorte de ''chip select'' pour le 82385, limitée aux accès mémoire. Cette entrée permettait de programmer des intervalles d'adresse auxquels ne pas répondre, en utilisant des circuits de décodage d'adresse adaptés. Il avait aussi une entrée X16, qui permettait d'identifier les accès soit à un composant 16 bits. De tels accès ne doivent pas être mis en cache, sans doute parce que cela ne collait pas avec la taille des lignes de cache (32 bits). Et cette entrée permettait d'inhiber ces accès 16 bits d'agir sur le cache, en utilisant le bit du bus de commande adéquat.
Le 82385 pouvait être intégré dans un système à deux processeurs, voire plus. Pour cela, chaque processeur avait son propre 82385 et sa SRAM rien qu'à lui. Il n'y avait pas de cache partagé entre les deux processeurs. Par contre, les deux caches étaient reliés au même bus système. Pour qu'ils ne se marchent pas sur les pieds, il y avait des circuits d'arbitrage pour gérer l'accès au bus. Un des deux 82385 était mis en mode maitre, l'autre était en mode esclave. Le 82385 maitre pouvait prendre le contrôle du bus, le 82385 esclave devait demander l'autorisation au premier pour accéder au bus système.
Le 82385 gérait une forme limitée de cohérence des caches par invalidation. Dès que le 82385 détectait une prise de contrôle du bus par autre chose que le processeur, il surveillait les adresses transmises sur le bus. En cas de succès de cache, la ligne de cache associée était invalidée. Au-delà de ça, le 82385 avait une entrée FLUSH, qui ordonnait une invalidation totale du cache. Si cette entrée est mise à 1, toutes les lignes de cache sont invalidées. Les ''tags'' sont marqués comme invalides, mais les lignes de cache elles-mêmes ne sont pas touchées.
===L'exemple des processeurs Intel de microarchitecture ''Broadwell''===
Un autre exemple est celui du cache L4 des processeurs Broadwell et de quelques processeurs séparés. Ces processeurs ont une organisation en ''chiplet'' où le processeur incorpore plusieurs puces séparées : une puce pour le processeur proprement dit, une puce nommée ''Crystal Well'' pour le cache L4, et une puce IO pour la communication avec la RAM et la carte mère. Le processeur incorporait un cache L4 de 128 mébioctets, composé de mémoire eDRAM, qui était dispersé entre ''Crystal Well'' et les autres puces. Les données du cache L4 étaient dans ''Crystal Well'', alors que les Tags étaient soit dans le processeur lui-même, soit dans la puce IO !
La puce ''Crystal Well'' était une mémoire DRAM adressable tout ce qu'il y a de plus basique, avec cependant quelques optimisations notables. Par exemple, elle avait deux bus séparés pour l'écriture et la lecture. De plus, elle avait une organisation interne avec 128 banques, contre moins d'une dizaine pour la DDR de l'époque et environ 32 banques pour la DDR5 moderne. Elle contenait aussi quelques circuits pour gérer son rôle de mémoire cache, mais rien en ce qui concerne la gestion des tags eux-mêmes.
Sur les processeurs de microarchitecture ''Broadwell'', les tags étaient placés dans le CPU et précisément dans le cache L3. À chaque accès mémoire au cache L3, les tags du cache L4 étaient consultés en parallèle. De fait, l'accès au cache L4 était assez rapide, malgré le fait que les données étaient dans une puce à part. Ajoutons à cela que le processeur et ''Crystal Well'' n'avaient pas la même finesse de gravure ni la même technologie de fabrication. Les tags étaient implémentés avec de la SRAM contre la DRAM pour les données, ce qui fait que la consultation des tags était plus rapide que l'accès aux données.
Par la suite, dans certains CPU de microarchitecture ''skylake'', les tags ont été déplacés en-dehors du processeur pour finir dans le contrôleur mémoire. En faisant cela, le cache L4 pouvait être utilisé par autre chose que le processeur, et notamment par la carte graphique intégrée au CPU. Avec ''broadwell'', le fait que les tags étaient consultés en cas d'accès au L3 empêchait au GPU intégré de consulter le cache L4. Mais en déplaçant les tags dans le contrôleur mémoire, ce n'est plus le cas vu que la carte graphique a aussi accès au bus mémoire. Par contre, le temps d'accès augmente comparé à la solution précédente. On n'accède pas aux tags du L4 en parallèle du L3 : à la place, il faut consulter les tags du L3, détecter un défaut de cache L3, et ensuite accèder aux tags.
===Les caches RAM-configurables===
Un autre avantage des caches splittés est qu'on peut les modifier pour servir à la fois de mémoire cache, mais aussi de ''local store'', de mémoire RAM de petite taille. Le fonctionnement est assez simple à comprendre. Lors d'un accès au cache, on accède aux tags, puis à la RAM interne au cache. Lors d'un accès au ''local store'', on contourne l'accès au tags et on accède à la RAM interne au cache directement. Il s'agit de la technique du '''cache RAM-configurable''. L'usage de cache RAM-configurable est fréquent sur les cartes graphiques récentes, qui incorporent un ou plusieurs processeurs multicoeurs, dont le cache L1 de données est un cache RAM-configurable.
[[File:Hydride cache - local store.png|centre|vignette|upright=2.0|Hydride cache - local store]]
===La compression de cache===
Une autre optimisation permise par les ''phased caches'' est l'implémentation de techniques de '''compression de cache''', qui visent à compresser des lignes de cache. L'intérêt est qu'on peut stocker plus de données dans le cache, à capacité égale. L'inconvénient est qu'on doit compresser/décompresser les lignes de cache, ce qui demande un circuit en plus et allonge les temps d'accès. En effet, le temps mis pour compresser/décompresser une ligne de cache s'ajoute au temps d'accès. Aussi, la compression de cache sert surtout pour les caches de bas niveau dans la hiérarchie mémoire, les gros caches aux temps d'accès assez longs.
Une première technique, assez simple à implémenter et peu couteuse en circuit, est celle de la '''compression des lignes de cache nulles'''. Elle compresse uniquement les lignes de cache qui ne contiennent que des zéros. L'idée est qu'on ajoute, dans la mémoire des tags, un bit de contrôle pour chaque ligne de cache appelé le bit ''null''. Il indique si la ligne de cache ne contient que des zéros. Quand on lit une ligne de cache, la mémoire des tags est accédée et on vérifie le bit ''null'' : s'il vaut 1, on n'accède pas à la mémoire cache de données et un multiplexeur envoie un zéro sur le port de lecture. Le bit ''null'' est fixé lors de l'écriture d'une ligne de cache : elle passe dans un comparateur avec zéro relié à la mémoire des tags. La comparaison avec zéro peut se faire en parallèle de l'écriture ou avant (dans ce cas, on n'écrit pas la ligne de cache dans le cache).
Les autres techniques de compression de cache permettent de compresser autre chose que des lignes de cache nulles. L'idée est qu'une ligne de cache physique peut par moment mémoriser plusieurs lignes de caches compressées. Par exemple, prenons un cache dont les lignes de cache font 64 octets. Il est possible de compresser deux lignes de cache pour qu'elles fassent chacune 32 octets, et les stocker dans une seule ligne de cache. Les deux lignes de cache auront des tags différents, mais pointeront sur la même ligne de cache physique. Et cela demande d'utiliser un ''phased cache'' dont la mémoire pour les tags est plus grande que la mémoire pour les données. Il n'y a donc plus une bijection entre tags et ligne de cache, mais une relation surjective. Chose qui n'est possible qu'avec un ''phased cache''. De plus, des bits de contrôles associés à chaque ''tag'' indiquent où se trouvent les lignes de cache compressées dans la ligne de cache : est-ce que c'est les 32 octets de poids fort ou de poids faible ?
[[File:Compression de cache.png|centre|vignette|upright=2|Compression de cache]]
Il ne semble pas que les techniques de compression de cache soient implémentées sur les processeurs modernes. Aucun n'utilise de compression de cache, à ma connaissance. Il faut dire que les techniques connues sont de mauvais compromis : le temps d'accès du cache augmente beaucoup, le cout en circuit pourrait être utilisé pour un cache non-compressé mais plus grand. Et notons que la compression de cache ne marche que si les données peuvent se compresser. Si ce n'est pas le cas, une partie de la mémoire des tags est inutilisée.
Une revue de la littérature académique sur la compression de cache est disponible via ce lien, pour les curieux :
* [https://inria.hal.science/hal-03285041 Understanding Cache Compression, par Carvalho et Seznec].
==Les caches adressés par somme et hashés==
Les caches adressés par somme sont optimisés pour incorporer certains calculs d'adresse directement dans le cache lui-même. Pour rappel, certains modes d'adressage impliquent un calcul d'adresse, qui ajoute une constante à une adresse de base. Généralement, l'adresse de base est l'adresse d'un tableau ou d'une structure, et la constante ajoutée indique la position de la donnée dans le tableau/la structure. Les caches hashés et les caches adressés par somme permettent de faire l'addition directement dans la mémoire cache. Voyons d'abord les caches hashés, avant de passer aux caches adressés par somme.
Sur les '''caches hashés''', l'addition est remplacée par une autre opération, par exemple des opérations bit à bit du style XOR, AND ou OR, etc. Seulement, utiliser des opérations bit à bit pose un problème : il arrive que deux couples Adresse/décalage donnent le même résultat. Par exemple, le couple Adresse/décalage 11101111/0001 donnera la même adresse que le couple 11110000/0000. Dit autrement, deux adresses censées être différentes (après application du décalage) sont en réalité attribuées à la même ligne de cache. Il est toutefois possible de gérer ces situations, mais cela demande des astuces de haute volée pour faire fonctionner la mémoire cache correctement.
Sur les '''caches adressés par somme''', le décodeur est modifié pour se passer de l'addition. Pour comprendre comment, il faut rappeler qu'un décodeur normal est composé de comparateurs, qui vérifient si l'entrée est égale à une constante bien précise. Sur un cache ordinaire, l'addition est faite séparément du décodage des adresses par le cache, dans l'unité de calcul ou dans l'unité de génération d'adresse.
[[File:Non sum adressed cache.png|centre|vignette|upright=2|Cache normal.]]
Mais les caches adressés par somme modifient le décodeur, qui est alors composé de comparateurs qui testent si la somme adresse + décalage est égale à une constante.
[[File:Cache adressé par somme.png|centre|vignette|upright=2|Cache adressé par somme.]]
Chaque circuit du décodeur fait le test suivant, avec K une constante qui dépend du circuit :
: <math>A + B = K</math>
Ce qui est équivalent à faire le test suivant :
: <math>A + B - K = 0</math>
En complément à deux, on a <math>- K = \overline{K} + 1</math>. En injectant dans l'équation précédente, on a :
: <math>A + B + \overline{K} + 1 = 0</math>
En réorganisant les termes, on a :
: <math>A + B + \overline{K} = - 1</math>
Il suffit d'utiliser un additionneur ''carry-save'' pour faire l'addition des trois termes. Rappelons qu'un tel additionneur fournit deux résultats en sortie : une somme calculée sans propager les retenues et les retenues en question. Notons que les retenues sont à décaler d'un cran, vu qu'elles sont censées s'appliquer à la colonne suivante. En notant la somme S et les retenues R, on a:
: <math>S + (R << 1) = - 1 </math>, le décalage d'un cran à gauche étant noté <math><< 1</math>.
Ensuite, -1 est codé avec un nombre dont tous les bits sont à 1 en complément à un/deux.
: <math>S + (R << 1) = 111 \cdots 111111</math>
[[File:Sum + retenue add.png|centre|vignette|upright=2|Sum + retenue add]]
Un simple raisonnement nous permet de savoir si le résultat est bien -1, sans faire l'addition <math>S + (R << 1)</math>. En effet, on ne peut obtenir -1 que si la somme est l'inverse des retenues : un 0 dans le premier nombre correspond à un 1 dans l'autre, et réciproquement. En clair, on doit avoir <math>\overline{S} = R << 1</math>. Pour vérifier cela, il suffit de faire un simple XOR entre la somme et les retenues décalées d'un cran. On a alors :
: <math>S \oplus (R << 1) = 111 \cdots 111111</math>
La comparaison avec -1 se fait avec une porte ET à plusieurs entrées. En effet, la porte donnera un 1 seulement si tous les bits d'entrée sont à 1, ce qui est ce qu'on veut tester.
Au final, l'additionneur pour l'addition adresse + décalage est remplacé par un additionneur carry-save suivi d'une couche de portes XOR et d'un comparateur avec une constante, ce qui économise de circuits et améliore les performances.
[[File:Final circuit of sum addressed cache.png|centre|vignette|upright=2|Cache adressé par somme.]]
En prenant en compte que la constante K est justement une constante, certaines entrées de l'additionneur carry-save sont toujours à 0 ou à 1, ce qui permet quelques simplifications à grand coup d’algèbre de Boole. Chaque additionneur complet qui compose l’additionneur carry-save est remplacée par des demi-additionneurs (ou par un circuit similaire). Autant dire que l'on gagne tout de même un petit peu en rapidité, en supprimant une couche de portes logiques. Le circuit de décodage économise aussi des portes logiques, ce qui est appréciable.
==Les caches à accès uniforme et non-uniforme==
Intuitivement, le temps d'accès au cache est le même pour toutes les lignes de cache. Il s'agit de cache appelés '''caches à accès uniforme''', sous-entendu à temps d'accès uniforme. Mais sur les caches de grande capacité, il arrive souvent que le temps de propagation des signaux varie fortement suivant la ligne de cache à lire. D'ordinaire, on se cale sur la ligne de cache la plus lente pour caler la fréquence d'horloge du cache, même si on pourrait faire mieux. Cependant, les '''caches à accès non uniforme''' ont une latence différente pour chaque ligne d'un même cache. Certaines lignes de cache sont plus rapides que d'autres.
Niveau terminologie, nous allons parler de caches UCA et NUCA : ''Uniform Access Cache'' pour les caches à accès uniforme, ''Non-Uniform Access Cache'' pour les caches à accès non-uniforme.
[[File:Caches UCA et NUCA.png|vignette|Caches UCA et NUCA.]]
Les caches NUCA et UCA sont souvent composés de plusieurs banques séparées, typiquement une par voie. Sur les caches UCA, les banques sont interconnectées avec le processeur de manière à ce que toutes les interconnexions ont la même longueur pour toutes les banques. Typiquement, les banques sont organisées en carré, avec les interconnexions qui partent du centre, avec une disposition en H, illustrée ci-contre
Mais avec les caches NUCA, ce n'est pas le cas. Les interconnexions sont simplifiées et ont des longueurs différentes. Les caches NUCA n'ont pas tous le même genre d'interconnexions, qui dépendent du cache NUCA. En général, les interconnexion forme un réseau avec des sortes de routeurs qui redirigent les données/commandes vers la bonne destination : cache ou processeur. Les banques plus proches du processeur sont accessibles plus rapidement que celles éloignées, même si la différence n'est pas énorme.
Les caches NUCA sont généralement associatifs par voie. Les plus simples utilisent une banque par voie pour le cache, ce qui fait que certaines voies répondent plus vite que les autres. La détection des succès de cache est alors plus rapide si la donnée lue/écrite est dans une voie/banque rapide. En théorie, les défauts de cache demandent de vérifier toutes les banques, et se calent donc sur la pire latence. Mais divers caches se débrouillent pour que ce ne soit pas le cas, soit en vérifiant les banquyes unes par une, soit par un mécanisme de recherche plus complexe.
Les caches NUCA sont surtout utilisés pour les caches L3 et L4, éventuellement les caches L2. Les caches L1 sont systématiquement des caches UCA, car la latence de l'accès au cache L1 est utilisée par le processeur pour décider quand lancer les instructions. Pour simplifier, le processeur peut démarrer en avance une instruction avant qu'une opérande soit lue dans le cache L1, de manière à ce que la donnée arrive en entrée de l'ALU pile en même temps que l'instruction. Une histoire d'exécution dans le désordre et d'émission anticipée des instructions qu'on détaillera dans une bonne dizaine de chapitres. Toujours est-il que tout est plus simple pour le processeur si le cache L1 a un temps d'accès fixe. Par contre, les caches L3 et L4 sont traités en attendant que les données arrivent, le processeur reprend l'exécution des instructions quand les caches L3 et L4 ont terminé de répondre, pas avant.
Avec l'association une banque = une voie, la correspondance ligne de cache → bloc de mémoire qui est statique : on ne peut pas déplacer le contenu d'une ligne de cache dans une autre portion de mémoire plus rapide suivant les besoins. Mais la recherche académique a étudié le cas où la correspondance entre une ligne de cache et une banque varie à l’exécution. Pour nommer cette distinction, on parle de caches S-NUCA (''Static NUCA'') et D-NUCA (''Dynamic NUCA'').
Intuitivement, on s'attend à ce que les caches D-NUCA soient plus performants que les caches S-NUCA. Les lignes de cache les plus utilisées peuvent migrer dans une banque rapide, alors que les lignes de cache moins utilisées vont dans une banque éloignée. Les lignes de cache se répartissent dans le cache dynamiquement dans les banques où elles sont le plus adaptées. Mais paradoxalement, le gain des caches D-NUCA est presque nul, voire insignifiant. La raison est que les caches D-NUCA doivent incorporer un système pour déterminer dans quelle banque se situe la donnée pour détecter les succès/défauts de cache, ainsi qu'un système pour migrer les données entre banques. Et ce système augmente le temps d'accès au cache, réduisant à néant l'intérêt d'un cache D-NUCA. Si on économise quelques microsecondes de temps d'accès en passant d'un cache UCA à un cache S-NUCA, ce n'est pas pour les perdre en passant à un D-NUCA. La majorité des caches D-NUCA sont donc en cours de recherche, mais ne sont pas utilisés en pratique.
==La tolérance aux erreurs des caches==
Une mémoire cache reste avant tout une mémoire RAM, bien que ce soit de la SRAM. Elle n'est pas parfaite et est donc sujette à des erreurs, qui peuvent inverser un bit ou l'effacer. De telles erreurs sont liées à des rayons cosmiques très énergétiques, à des particules alpha produites par le packaging ou le métal deu circuit intégré, peu importe : l'essentiel est qu'ils inversent parfois un bit. Les mémoires modernes savent se protéger contre de telles erreurs, en utilisant trois moyens.
===Les mémoires caches ECC et à bit de parité===
Le premier moyen est l'usage de codes correcteurs d'erreurs, qui ajoutent un ou plusieurs bits à la ligne de cache, dans les bits de contrôle. Les bits ajoutés dépendent de la donnée mémorisée dans le byte, et servent à détecter une erreur, éventuellement à la corriger. Le cas le plus simple ajoute un simple bit de parité pour chaque byte et se contente de détecter les erreurs dans les corriger. Les autres codes ECC permettent eux de corriger des erreurs, mais ils demandent d'ajouter au moins deux bits par byte, ce qui a un cout en circuit plus élevé.
Un simple bit de parité permet de détecter qu'un bit a été inversé, mais ne permet pas de corriger l'erreur. En soi, ce n'est pas un problème. Si une erreur est détectée, on considère que la ligne de cache est invalide. Le cache gère la situation comme un défaut de cache et va chercher la donnée valide en mémoire RAM. Le cout en circuits est donc faible, mais les défauts de cache sont plus nombreux. Les codes ECC sont eux capables de corriger les erreurs, si elles ne modifient pas trop de bits d'un coup. Par contre, ils utilisent deux à trois bits par octet, ce qui a un cout en circuits loin d'être négligeable. Il y a donc un compromis entre défauts de cache et cout en circuits.
La gestion de l'ECC est différente suivant le niveau de cache. Généralement, le cache L1 n'utilise pas l'ECC mais se contente d'un simple bit de parité pour éviter la corruption de ses données. Le cache étant petit, les corruptions de données sont assez rares, et les défauts de cache induits faibles. Il est plus important d'utiliser un code de détection d'erreur simple, rapide, qui ne ralentit pas le cache et n'augmente pas sa latence. Si une ligne de cache est corrompue, il a juste à aller lire la ligne depuis le cache L2, ou un niveau de cache inférieur. Du moins, c'est possible sur le cache en question est un cache inclusif et/ou ''write-through''.
Par contre, le niveau de cache L2 et ceux en-dessous utilisent presque systématiquement une mémoire SRAM ECC. La raison principale étant que ce sont des caches assez gros, pour lesquels la probabilité d'une erreur est assez élevée. Plus une mémoire a de bits et prend de la place, plus il y a une chance élevée qu'un bit s'inverse. Et vu que les caches L2/L3/L4 sont par nature plus lents et plus gros, ils peuvent se permettre le cout en performance lié à l'ECC, idem pour le cout en circuit. Sans compter qu'en cas d'erreur, ils doivent aller lire la ligne de cache originelle en mémoire RAM, ce qui est très lent ! Mieux vaut corriger l'erreur sur place en utilisant l'ECC.
===L'usage du ''memory scrubbing'' sur les caches===
La plupart des erreurs ne changent qu'un seul bit dans un byte, mais le problème est que ces erreurs s'accumulent. Entre deux accès à une ligne de cache, il se peut que plusieurs erreurs se soient accumulées, ce qui dépasse les capacités de correction de l'ECC. Dans ce cas, il existe une solution appelée le ''memory scrubbing'', qui permet de résoudre le problème au prix d'un certain cout en performance.
Pour rappel, l'idée est de vérifier les lignes de caches régulièrement, pour éviter que les erreurs s'accumulent. Par exemple, on peut vérifier chaque ligne de cache toutes les N millisecondes, et corriger une éventuelle erreur lors de cette vérification. En faisant des vérifications régulières, on garantir que les erreurs n'ont pas le temps de s'accumuler, sauf en cas de malchance avec des erreurs très proches dans le temps. Il ne s'agit pas d'un rafraichissement mémoire, car les SRAM ne s'effacent pas), mais ça a un effet similaire.
Et évidemment, le ''memory scrubbing'' a un cout en performance. On peut faire une comparaison avec le rafraichissement mémoire : les rafraichissement réguliers réduisent les performances, car cela fait des accès en plus. Des accès qui sont de plus timés à des instants bien précis qui ne sont pas forcément les plus adéquats. Il est possible qu'un rafraichissement ait lieu en même temps qu'un accès mémoire et le rafraichissement a la priorité, ce qui réduit les performances. La même chose arrive avec les vérifications du ''memory scrubbing''. Malgré tout, la technique a été utilisée sur les caches de certains processeurs commerciaux, dont des processeurs AMD Athlon et Athlon 64. Elle est surtout utilisable sur les caches L2/L3, pour lesquels le cout du pseudo-rafraichissement est acceptable.
==Un exemple de cache : le cache d'instruction==
La grande majorité des processeurs utilise deux caches L1 séparés : un '''cache d'instructions''' dédié aux instructions, et un autre pour les données. Une telle organisation permet de charger une instruction tout en lisant une donnée en même temps. Notons que seul le cache L1 est ainsi séparé entre cache de données et d'instructions.
Le cache d’instruction se situe en théorie entre l'unité de chargement et l'unité de décodage. En effet, ce cache prend en entrée une adresse et fournit une instruction. L'adresse est fournie par le ''program counter'', l'instruction est envoyée dans l'unité de décodage. Le cache se situe donc entre les deux. Le cache de données L1 est connecté au chemin de données, et notamment aux unités de communication avec la mémoire, pas au séquenceur.
[[File:Caches L1 et positions dans le processeur.png|centre|vignette|upright=2.5|Caches L1 et positions dans le processeur]]
Les deux caches sont reliés au processeur par des bus séparés, l'ensemble ressemble à une architecture Harvard, mais où les caches remplacent les mémoires RAM/ROM. Le cache d'instruction prend la place de la mémoire ROM et le cache de données prend la place de la mémoire RAM. Évidemment, il y a des niveaux de caches en dessous des caches de données/instruction, et ceux-ci contiennent à la fois données et instructions, les deux ne sont pas séparées dans des mémoires/caches séparés. Raison pour laquelle l'ensemble est appelé une '''architecture Harvard modifiée'''. Architecture Harvard, car l'accès aux données et instructions se font par des voies séparées pour le processeur, modifiée car la séparation n'est effective que pour le cache L1 et pas les autres niveaux de cache, et encore moins la RAM.
Sur les processeurs modernes, il arrive très souvent que le processeur doive charger une instruction et lire/écrire une donnée en même temps. Et à vrai dire, c'est la règle plus que l'exception. L'usage d'une architecture Harvard modifiée permet cela très facilement : on peut accéder au cache d'instruction via un bus, et au cache de donnée avec l'autre
===Pourquoi scinder le cache L1 en cache d'instruction et de données===
L'usage d'un cache d’instruction séparé du cache de données est à contraster avec l'usage d'un cache L1 multiport unique, capable de mémoriser à la fois instructions et données. Les deux solutions sont possibles ont été utilisées. Les premiers processeurs avaient un cache L1 unique et multiport, mais ce n'est plus le cas sur les processeurs modernes, car les contraintes ne sont pas les mêmes.
Le compromis à faire est celui entre deux petits caches rapides et un gros cache plus lent. Pour rappel, plus un cache est petit, plus il est rapide et chauffe moins. Donc au lieu d'utiliser, par exemple, un gros cache lent de 64 Kibioctets, on utilise deux caches de 32 kibioctets, plus rapides. La capacité totale est la même, mais le temps d'accès plus faible. En termes de temps d'accès, la meilleure solution est celle des deux caches simple port. Mais pour ce qui est de l'économie de circuits, c'est moins évident. Entre deux mémoires simple port et une mémoire multiport, la différence en termes de transistors est ambigüe et dépend de la capacité des caches. La différence est surtout notable pour les gros caches, moins pour les petits caches.
Il faut aussi tenir compte de la capacité effective. Avec deux caches séparés, la répartition de la capacité du cache L1 est fixée une bonne fois pour toutes. Par exemple, avec un cache d'instruction de 32 KB et un cache de données de 32 KB, impossible d'allouer 40 KB aux données et 20 aux instructions. Alors qu'avec un cache L1 unique de 64 KB, on pourrait le faire sans soucis. La répartition se fait naturellement, en fonction de la politique de remplacement du cache et est proche de l'optimal. C'est là un désavantage des caches d'instructions/données séparés : une capacité effective moindre.
Tout cela explique pourquoi le cache L1 est le seul à être ainsi scindé en deux, avec une séparation entre instructions et données : les contraintes au niveau du cache L1 et L2 ne sont pas les mêmes. Pour les caches L1, le temps d'accès est plus important que la capacité, ce qui favorise les caches séparés. Par contre, pour les caches L2/L3/L4, le temps d'accès n'est pas déterminant, alors que la capacité effective et l'économie en circuits sont significatives.
===La connexion des caches L1 avec le cache L2===
Pour les connexions avec le cache L2, tout dépend du processeur. Certains utilisent un cache L2 multiport, qui permet aux deux caches L1 de lire ou écrire dans le cache L2 simultanément.
[[File:Cache d'instructions.png|centre|vignette|upright=1.5|Cache d'instructions.]]
Si le cache L2 ne gère pas les accès simultanés, il n'y a qu'un seul bus relié aux caches L1 et au cache L2. On doit effectuer un arbitrage pour décider quel cache a la priorité, chose qui est réalisé par un circuit d'arbitrage spécialisé.
[[File:Circuit d'arbitrage du cache.png|centre|vignette|upright=1.5|Circuit d'arbitrage du cache.]]
Généralement, les caches d'instructions peuvent se permettre d'être plus petits que les caches de données, car les programmes sont souvent plus petits que les données manipulées. Songez que des programmes de quelques mébioctets peuvent parfois remplir la RAM avec plusieurs gibioctets de données. Lancez votre navigateur internet et ouvrez une page web un peu chargée, pour vous en convaincre !
===Les spécificités du cache d'instruction : lecture seule, bloquant, etc===
Les instructions sont rarement modifiées ou accédées en écritures, contrairement aux données. Et cela permet d'utiliser un cache simplifié pour les instructions. Autant un cache généraliste doit permettre les lectures et écritures depuis le processeur (avec les échanges avec la RAM), autant un cache d'instruction peut se contenter des lectures provenant du CPU et des échanges avec la RAM. Le cache d'instructions est donc très souvent en « lecture seule » : le processeur ne peut pas écrire dedans, mais juste le lire ou charger des instructions dedans.
Un cache d'instruction est donc plus simple qu'un cache pour les données : on peut retirer les circuits en charge de l'écriture (mais on doit laisser un port d'écriture pour charger les instructions dedans). Le gain en circuits permet d'utiliser un cache d'instruction plus gros ou au contraire de laisser de la place pour le cache de données. Le gain en termes de capacité compense alors un peu les inconvénients des caches séparés.
Par contre, cela complique la gestion du code automodifiant, c'est-à-dire des programmes dont certaines instructions vont aller en modifier d'autres, ce qui sert pour faire de l'optimisation ou est utilisé pour compresser ou cacher un programme (les virus informatiques utilisent beaucoup de genre de procédés). Quand le processeur exécute ce genre de code, il ne peut pas écrire dans ce cache L1 d'instructions, mais doit écrire dans le cache L2 ou en RAM, avant de recharger les instructions modifiées dans le cache L1. Cela qui prend du temps et peut parfois donner lieu à des erreurs si le cache L1 n'est pas mis à jour.
Les algorithmes de remplacement des lignes de cache optimaux pour les données ne le sont pas pour les instructions, de même que la taille optimale du cache, la taille des lignes de cache optimale, ou même les algorithmes de préchargement. Par exemple, pour le remplacement des lignes de cache, un simple algorithme LRU est presque optimal pour les instructions, autant il peut donner de mauvaises performances quand on manipule beaucoup de tableaux. Cela justifie d'utiliser des caches spécialisés pour chacune. On peut adapter le cache d'instruction à son contenu, ce qui le rend plus rapide ou plus petit à performance égale.
Les caches d'instructions sont généralement des caches bloquants. Il ne servirait à rien de rendre un cache d'instruction non-bloquant, le cout en circuits ne se traduirait pas par une augmentation significative des performances. À l'opposé, les caches de données sont non-bloquants sur les architectures modernes, pour des raisons de performance. Ce qui rend la séparation assez intéressante, les deux caches ayant des besoins différents et des implémentations différentes, cela permet d'optimiser le cout en transistors des caches.
===L'impact du cache d'instruction sur les performances===
Sur les architectures conventionnelles, le cache d'instruction a plus d'impact sur les performances que le cache de données. La raison principale est que les instructions ont une meilleure localité spatiale et temporelle que pour les données. Pour la localité spatiale, les instructions consécutives se suivent en mémoire, alors que rien ne garantit que des données utilisées ensemble soient regroupées en mémoire. Pour localité temporelle, elle est très variable pour les données, mais très courante pour les instructions du fait de l'usage fréquent des boucles et des fonctions.
: La présence de branchements atténue la localité temporelle des instruction, sauf que la majorité des branchements sautent à un endroit très proche, seuls les appels de fonction brisent la localité spatiale.
La conséquence est qu'il arrive que certains CPU aient un cache L1 d'instruction plus gros que celui pour les données. On parle alors de '''cache L1 asymétriques'''. Un exemple est celui des processeurs AMD de microarchitecture Zen, dont le cache d'instruction était deux fois plus gros que le cache de données. Leur cache d'instruction faisait 64 kibioctets, contre seulement 32 pour le cache de données.
D'ailleurs, il existe des processeurs assez extrêmes qui se contentent d'un cache d'instruction unique, sans cache de données. C'est le cas sur les processeurs vectoriels ou les GPU que nous verrons dans les chapitres de fin de ce wikilivres. De tels processeurs sont spécialisés dans la manipulation de tableaux de données, traitement qui a une faible localité temporelle. En conséquence, utiliser un cache de données n'est pas vraiment utile, voire peu être contreproductif, alors qu'un cache d’instruction fonctionne parfaitement.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Compléments sur les mémoires de masse
| prevText=Compléments sur les mémoires de masse
| next=Le préchargement
| nextText=Le préchargement
}}
</noinclude>
m748h9w91k1w6os71gfve8b2wvxpf53
Fonctionnement d'un ordinateur/Les architectures à parallélisme de données
0
65960
773373
772242
2026-09-27T21:51:40Z
Mewtow
31375
/* Les instructions SIMD : les points communs entre tous les processeurs SIMD */
773373
wikitext
text/x-wiki
Nous allons maintenant aborder le parallélisme de données, qui consiste à traiter des données différentes en parallèle. De nombreuses situations s'y prêtent relativement bien : traitement d'image, manipulation de sons, vidéo, rendu 3d, etc. Mais pour exploiter ce parallélisme, il a fallu concevoir des processeurs adaptés. Les architectures les plus simples exécutent une instruction sur plusieurs données en parallèle, à l'intérieur d'un processeur. Ces architectures exploitent le parallélisme de données au niveau de l'unité de calcul, celle-ci pouvant exécuter un même calcul sur des données différentes en parallèle.
Si on omet quelques exceptions, on peut classer ces architectures dans plusieurs catégories principales : les processeurs à instructions SIMD, les processeurs à instructions SIMD à prédicat, les processeurs SIMT, les processeurs vectoriels. Si les processeurs vectoriels sont assez rares de nos jours, tous les processeurs récents incorporent des instructions SIMD. Quant au SIMT, il est utilisé sur les cartes graphiques modernes.
==Les instructions SIMD : les points communs entre tous les processeurs SIMD==
Les processeurs modernes fournissent presque tous des '''instructions SIMD''', qui sont capables de traiter plusieurs éléments en parallèle. Elles travaillent sur des nombres entiers ou flottants regroupés dans ce qu'on appelle un ''vecteur'', qui sont eux-mêmes stockés dans des registres spécialisés. En général, tous les vecteurs ont une taille fixe, peu importe leur contenu. Cela implique que suivant la taille des données à manipuler, on pourra en placer plus ou moins dans un vecteur. Par exemple, un vecteur de 128 bits pourra contenir 4 entiers de 32 bits, 4 flottants 32 bits, ou 8 entiers de 16 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
Les vecteurs sont stockés dans des '''registres vectoriels''', aussi appelés '''registres SIMD'''. Un registre vectoriel peut contenir un vecteur complet, pas plus. En conséquence, ils ont une taille assez importante : ils font généralement 128,,256, voire 512 bits, comparé aux 32/64 bits des registres scalaires. Le résultat est que les processeurs SIMD modernes ont des registres SIMD séparés des registres entiers/flottants normaux. Un défaut de cette organisation est que les registres vectoriels sont des registres en plus, qui doivent être sauvegardés lors des commutations de contexte, lors des interruptions, lors des appels systèmes, etc.
{|
|+ Comparaison entre un processeur sans registres vectoriels, et avec registres vectoriels.
|[[File:Non-SIMD cpu diagram1.svg|vignette|upright=1.5|CPU Non-SIMD]]
|[[File:SIMD cpu diagram1.svg|vignette|upright=1.5|CPU SIMD]]
|}
Les instructions SIMD peuvent être rassemblées en deux grands groupes : les horizontales et les verticales.
Les '''instructions SIMD verticales''' travaillent en parallèle sur les éléments qui sont "à la même place" dans deux vecteurs. Elles peuvent additionner ou multiplier deux vecteurs, par exemple. Pour prendre l'exemple d'une instruction d'addition vectorielle, celle-ci va additionner ensemble les données qui sont à la même place dans deux vecteurs, et placer le résultat dans un autre vecteur, à la même place.
Les '''instructions SIMD horizontales''' partent d'un vecteur et ont pour résultat un simple nombre. Elles peuvent calculer la somme ou le produit des éléments d'un vecteur, renvoyer le nombre d’éléments nuls dans un vecteur, etc.
[[File:Instructions SIMD.png|centre|vignette|upright=2.5|Instructions SIMD]]
Les instructions SIMD sont difficilement utilisables dans des langages de haut niveau et c'est donc au compilateur de traduire un programme avec des instructions vectorielles. Les transformations qui permettent de traduire des morceaux de programmes en instructions vectorielles (déroulage de boucles, strip-mining) portent le nom de vectorisation.
===Les instructions SIMD arithmétiques verticales===
Les instructions SIMD sont essentiellement des instructions arithmétiques, comme des additions, des soustractions, des multiplications, éventuellement des opérations bit à bit, et quelques autres. Suivant la taille des données, et le type de celle-si, on devra effecteur des instructions différentes. Par exemple, on devra utiliser deux instructions d'addition différentes suivant qu'on manipule des flottants 64 bits ou des entiers 32 bits. De même, l'instruction pour additionner des vecteurs d'entiers de 16 bits sera différente de celle manipulant des vecteurs d'entiers de 32 bits.
L'addition et la multiplication peuvent générer des débordements d'entier. Pour les instructions non-SIMD, le débordement d'une addition est géré par un bit de retenue, stocké dans le registre d'état. Mais pour les instructions SIMD, ce n'est pas une solution très facile à implémenter. À la place, les additions et soustractions utilisent généralement l'arithmétique saturée, à savoir que lorsqu'une addition déborde, le résultat est la valeur maximale représentable dans un entier.
Pour la multiplication, les instructions non-SIMD génère un résultat qui est codé sur le double du nombre de bits. Une multiplication de deux nombres 64 bits donnera un résultat sur 128 bits, par exemple. Pour gérer cela, les multiplications non-SIMD stockent ce résultat dans deux registres, ou ne conservent que les 64 bits de poids faible. D'autres solutions sont possibles, mais ces deux là sont les plus utilisées. Pour les instructions SMID, une autre solution est préférée car plus simple pour de telles instructions : fournir deux instructions : une qui calcule les 64 bits de poids faible du résultat, une autre pour les 64 bits de poids fort.
Les processeurs SIMD étant utilisés pour du traitement d'image ou du rendu 2D/3D, ils supportent généralement des opérations mathématiques assez complexes sur des nombres flottants. Il n'est pas rare d'avoir des instructions SIMD pour le calcul de l'inverse d'un nombre, de sa racine carrée, l'inverse d'une racine carrée, des fonctions trigonométriques, etc. L'implémentation matérielle de telles instructions est généralement très complexe et gourmande en circuits, ce qui fait que les concepteurs de processeurs rusent. Sauf sur certains processeurs assez peu fréquents, les instructions mathématiques complexes ne calculent pas un résultat exact, mais une approximation du résultat. Utiliser un résultat approximatif ne pose pas de problème pour de telles applications, le rendu d'image ou 3D s'en accommodant parfaitement, contrairement au calcul scientifique.
===Les instructions SIMD de manipulation de données intra-vecteur===
Après avoir vu les instructions SIMD verticales, il est temps de voir les instructions SIMD horizontales. Pour rappel, celles-ci travaillent un vecteur unique, dont elles réarrangent ou modifient les éléments. Tout cela sera plus parlant une fois qu'on aura donné quelques exemples. Mais avant toute chose, nous allons séparer les instructions horizontales en deux sous-types, fondamentalement différents. Le premier sous-type change de place les données dans un vecteur. Le second type est celui des instructions horizontales arithmétiques/logiques habituelles, qu'on verra plus tard. La distinction entre les deux est très importante, comme on le verra plus tard.
[[File:SIMD instruction data movement - One operand.png|vignette|upright=1.5|Instructions SIMD de mouvement de données à une opérande.]]
Les instructions SIMD horizontales les plus communes bougent des données à l'intérieur des vecteurs, ce qui leur vaut le nom d''''instructions de manipulation de vecteur'''. Les plus simples sont celles qui ont une seule opérande, à savoir qui prennent un vecteur comme opérande et renvoient un vecteur résultat.
Les plus intuitives sont les instructions de '''permutation''', dont le nom est assez explicite. Elles changent de place des données dans un vecteur. Dans le cas le plus compliqué, elles prennent deux vecteur : le vecteur dans lequel faire la permutation, et un vecteur qui indique comment faire la permutation. Le vecteur précise, pour chaque élement du vecteur, où il doit aller après la permutation. L'instruction effectue la permutation des entiers/flottants/octets dans le vecteur.
Les instructions '''''compress''''' et '''''expand''''' sont elles aussi spécifiques aux vecteurs. L'instruction ''compress'' prend un vecteur en opérande, sélectionne certaines valeurs présentes dedans, et les stocke dans un vecteur de sortie. Elle regroupe les valeurs sélectionnées dans le début du vecteur, et laisse les cases inoccupées à 0. Par exemple, pour un vecteur de 16 entiers, elle permet de sélectionner 5 entiers dedans, et les place dans un vecteur résultat qui ne contient que ces 5 valeurs au début du vecteur, le reste est à 0. L'instruction ''expand'' fait l'inverse : elle prend un vecteur crée par l'instruction ''compress'' (ou du moin qui a le même format), prend toutes les valeurs non-nulles dedans, et les disperse dans un vecteur de sortie, en les mettant à la place désirée.
Les autres instructions SIMD horizontales prennent deux opérandes, voire aucune opérande vectorielle (elles prennent un nombre, pas un vecteur).
Les instructions '''''shuffle''''' prennent plusieurs vecteurs, sélectionnent les éléments adéquats dans chaque vecteur, et les regroupe dans un vecteur résultat. Par exemple, prenons 2 vecteurs de 16 entiers chacun. Une instruction ''shuffle'' peut prendre 8 entiers dans chaque vecteur et les regrouper dans un vecteur résultat unique de 16 entiers. Ou encore, elle peut prendre trois vecteurs de 16 flottants chacun, prendre 4 flottants dans le premier, 10 dans le second et 2 dans le troisième, et regrouper le tout dans un vecteur résultat de 16 flottants.
Il arrive que les deux cas précédents correspondent à des cas séparés, à deux instructions de type ''shuffle'', mais qui fonctionnent différemment. Le premier type prend les N premiers éléments du premier vecteur, puis prend les élements manquant à partir de la fin du vecteur. Le second type prend un élément sur deux dans chaque vecteur, mais avec un décalage d'un rang entre les deux vecteurs.
Il y a aussi les instructions dites de '''''broadcast''''', qui copient une valeur unique dans un vecteur entier. Elles sont surtout utilisées pour initialiser des tableaux avec une valeur unique, ou pour remplir un vecteur avec des données prédéterminées pour certains calculs impliquant des constantes.
[[File:SIMD instruction exemple.png|centre|vignette|upright=1.5|Instructions SIMD de mouvement de données qui ne sont pas à une opérande.]]
===Les instructions SIMD de réduction===
Il existe des instructions horizontales de type arithmétiques. La plus simple est celle qui additionne les différents entiers/flottants d'un vecteur. Elle est utilisée pour accélérer une opération précise : faire la somme d'un tableau d'entier/flottants. On peut aussi citer les opérations maximum et minimum, qui renvoient le plus grand/petit élément d'un vecteur. Un point important avec ces opérations est qu'elles prennent un vecteur, mais renvoie un résultat unique, un scalaire, un nombre entier/flottant seul. Elles sont parfois appelées des '''instructions de réduction'''.
Les instructions de réduction sont généralement la spécificité des processeurs vectoriels, les processeurs SIMD récents n'en ont pas. La raison est que leur implémentation matérielle est généralement assez compliquée. Contrairement aux instructions verticales, elles ont une forme de parallélisme de donnée très limitée. Elles ne travaillent pas exactement sur des données indépendantes, elles font des calculs dont le résultat est utilisé par d'autres, il y a des dépendances de données entre éléments du vecteur.
Cependant, il faut savoir que l'on peut émuler une instruction de réduction en utilisant des instructions verticales couplées à des instructions de manipulation de vecteur. Par exemples, prenons le cas d'une instruction de réduction qui additionne entre eux les éléments d'un vecteur. On peut l'émuler en utilisant une addition verticale entre deux vecteurs, et une instruction ''compress''. L'idée est la suivante : on coupe le vecteur initial en deux avec deux instructions ''compress'', ce qui donne deux sous-vecteurs. Puis, on additionne les deux vecteurs résultat entre eux. Puis on répète les deux étapes précédentes jusqu'à obtenir la somme finale.
Le fait que l'on puisse émuler les instructions de réduction est la raison principale qui explique que les processeurs SIMD récents n’intègrent pas d'instructions de réduction. Ils se débrouillent avec des instructions SIMD de manipulation de vecteur, et des instructions SIMD verticales. C'est un bon compromis entre cout en circuits et performance.
===Les accès mémoire===
La gestion des accès mémoire est assez hétéroclite, la façon de faire étant différente selon les architectures. Dans le cas le plus simple, les données d'un vecteur sont contigües en mémoire et les instructions ont juste à préciser l'adresse mémoire du début du vecteur, qui est généralement dans un registre d'adresse spécialisé, ou un registre non-SIMD. Avec des vecteurs de 8 octets, toute instruction d'accès mémoire de ce type va lire ou écrire des blocs de 8 octets. L'adresse de départ de ces blocs est soumise à des contraintes d'alignement sur les jeux d'instructions comme le SSE, le MMX, etc. La raison à cela est que gérer des accès non alignés en mémoire rend les circuits de lecture/écriture en mémoire plus complexes. En contrepartie, ces contraintes compliquent l'utilisation des instructions SIMD par le compilateur.
D'autres modes d'adressage des vecteurs permettent à une instruction de charger des données dispersées en mémoire pour les rassembler dans un vecteur. On peut notamment citer l'existence d'accès mémoires en stride et en scatter-gather.
L''''accès en stride''' regroupe des données séparées par un intervalle régulier d'adresses. Ce mode d'accès a besoin de l'adresse initiale, de celle du premier élément du vecteur, et de la distance entre deux données en mémoire. Il permet aux instructions de mieux gérer les tableaux de structures, ainsi que les tableaux multi-dimensionnels. Lorsqu'on utilise de tels tableaux, il arrive assez souvent que l'on n'accède qu'à des éléments tous séparés par une même distance. Par exemple, si on fait des calculs de géométrie dans l'espace, on peut très bien ne vouloir traiter que les coordonnées sur l'axe des x, sans accès sur l'axe des y ou des z. Les instructions d'accès mémoire en enjambées gèrent de tels cas efficacement.
Les processeurs SIMD incorporent aussi les accès en '''''scatter-gather'''''. De tels accès prennent en opérande un vecteur contenant des adresses mémoires, et lit/écrit chacune de ses adresses mémoire indépendamment. Les accès en ''scatter-Gather'' peuvent être vus comme une généralisation de l'adressage indirect à registre aux vecteurs, chaque élément du vecteur étant adressé via adressage indirect. Les accès en ''gather'' sont des lectures : elles prennent un vecteur d'adresse, lisent chaque adresse, et regroupent toutes les données lues dans un vecteur, dans un registre vectoriel. Les accès en ''scatter'' sont l'équivalent pour les écritures. Ils prennent un vecteur d'opérande pour les données à écrire, un vecteur pour les adresses.
[[File:Cuda4.png|centre|vignette|upright=2|Adressage en scatter-gather]]
Un autre version des accès en ''scatter-gather'' n'utilise pas des adresses mémoire, amis des indices. Le vecteur opérande ne contient pas des adresses, mais des indices qui sont combinés à une adresse de base pour calculer plusieurs adresses. Un tel mode d'adressage permet de faciliter l'implémentation de certaines structures de données, appelées des vecteurs de Liffe. Ils sont très utilisés pour gérer les matrices creuses, des matrices où une grande partie des éléments sont nuls et où, dans un souci d'optimisation, seuls les éléments non nuls de la matrice sont stockés en mémoire.
Lors d'une instruction de ''scatter'' ou de ''gather'', tous les accès mémoire n'ont pas lieu en même temps, certains accès mémoire seront terminés avant les autres. Les accès peuvent se faire dans le désordre et de nombreuses optimisations matérielle en profitent pour gagner en performance. Il existe cependant un cas où les accès mémoire doivent se faire dans un ordre bien précis, généralement du premier élément vers le dernier : lorsqu'une instruction ''scatter'' effectue plusieurs écritures à la même adresse. C'est parfaitement possible, et c'est le seul cas où il est intéressant de forcer un ordre pour les écritures.
En théorie, une instruction SIMD est un tout, c'est à dire qu'elle ne doit pas modifier les registres SIMD avant que tous les accès mémoire se soient terminés. Cependant, il est possible qu'une partie des accès mémoire déclenchent des défauts de page ou lèvent des exceptions matérielles, alors que les autres se sont terminés. En théorie, le processeur est censé se débarrasser du résultat des accès mémoire terminés. Mais ce serait un gachis de performance, ce qui fait que le processeur viole la règle voulant que les registres SIMD soient mis à jour en une seule fois à la fin de l'instruction de ''scatter-gather''. Dès qu'un accès mémoire se termine, il écrit son résultat dans le registre SIMD, à l'endroit adéquat.
Cependant, ce comportement pose un problème. Lorsqu'une exception survient, comme lors d'un défaut de page, le processeur exécute la routine d'interruption associée, puis redémarre l'instruction fautive, le second essai étant le bon. Ici, le processeur ne réexecute pas l'instruction à l'identique, mais seulement les accès non-terminés. Mais comment le processeur sait-il quelles accès mémoire redémarrer ? Il doit pour cela mémoriser quels sont les accès mémoire terminés. Il utilise pour cela un registre architectural appelé le '''masque de complétion'''. Le registre est forcément architectural car il doit être préservé lors d'un changement de contexte, l’exécution de l'exception matérielle forçant un changement de contexte.
===Exemple avec les processeurs x86===
[[File:PD-20060908-SSE3-01.svg|vignette|Chronologie des extensions x86 SIMD.]]
Après avoir vu la théorie sur les instructions SIMD, il est temps de voir un exemple concret : celui des instructions SIMD des processeurs x86, présents dans nos PC. Le jeu d'instruction des PC qui fonctionnent sous Windows est appelé le x86. C'est un jeu d'instructions particulièrement ancien, apparu en 1978. Depuis, des '''extensions x86''' ont ajouté des instructions au x86 de base. On peut citer par exemple les extensions MMX, SSE, SSE2, ''3dnow!'', etc.
Les premières instructions SIMD furent fournies par une extension x86 du nom de MMX, introduite par Intel en 1996 sur le processeur Pentium MMX. Le MMX a perduré durant quelques années avant d'être remplacé par les extensions SSE. La même année, AMD sorti d'expansion ''3DNow!'', qui ajoutait 21 instructions SIMD similaires à celles du MMX. Celui-ci fût suivi du ''3DNow!+'' quelques années plus tard. Le SSE fût ensuite décliné en plusieurs versions avant d'être "remplacé" par l'AVX. Une grande quantité de ces extensions x86 sont des ajouts d'instructions SIMD.
L’'''extension MMX''' ajoutait pas mal d'instructions SIMD assez basiques, essentiellement des instructions arithmétiques : addition, soustraction, opérations logiques, décalages, rotations, mise à zéro d'un registre, etc. La multiplication est aussi supportée, mais avec quelques petites subtilités, via l'instruction PMULLW. Les instructions MMX ne mettent pas le registre d'état à jour et ne préviennent pas en cas d'overflow ou d'underflow si ceux-ci arrivent (pour les instructions qui ne travaillent pas en arithmétique saturée).
Le MMX introduisait 8 registres vectoriels, du nom de MM0, MM1, MM2, MM3, MM4, MM5, MM6 et MM7. Ils ne pouvaient contenir que des nombres entiers et faisaient 64 bits. Ils avaient cependant un léger défaut, qui a nui à l'adoption du MMX. Pour rappel, avant le MMX, les flottants étaient gérés par l'extension x87, qui définit 8 registres flottants de 80 bits. Et chaque registre MMX correspondait aux 64 bits de poids faible d'un registre flottant x87 ! Le système d'''alias'' de registres typique des CPU x86 a encore frappé ! En conséquence, il était impossible d'utiliser en même temps l'unité de calcul flottante et l'unité MMX. Par contre, sauvegarder les registres lors d'un changement de contexte, d'une interruption, ou d'un appel de fonction était très simple : la sauvegarde des registres de la FPU x87 suffisait.
[[File:MMX-FPU-Register.JPG|centre|vignette|upright=2|Registres MMX et FPU x87.]]
[[File:XMM registers.svg|droite|vignette|XMM registers]]
Dans les années 1999, une nouvelle extension SIMD fit son apparition sur les processeurs Intel Pentium 3 : le '''Streaming SIMD Extensions''', abrévié SSE. Ce SSE fut ensuite complété, et différentes versions virent le jour : le SSE2, SSE3, SSE4, etc. Cette extension fit apparaitre 8 nouveaux registres, les registres XMM. Sur les processeurs 64 bits, ces registres sont doublés et on en trouve donc 16. En plus de ces registres, on trouve aussi un registre d'état qui permet de contrôler le comportement des instructions SSE : celui contient des bits qui permettront de dire au processeur que les instructions doivent arrondir leurs calculs d'une certaine façon, etc. Ce registre n'est autre que le registre MXCSR. Chose étrange, seuls les 16 premiers bits de ce registre ont une utilité : les concepteurs du SSE ont surement préférés laisser un peu de marge au cas où.
La première version du SSE contenait assez peu d'instructions : seulement 70. Le SSE première version ne fournissait que des instructions pouvant manipuler des paquets contenant 4 nombres flottants de 32 bits (simple précision). Je ne vais pas toutes les lister, mais je peux quand-même dire qu'on trouve des instructions arithmétiques de base, avec pas mal d'opérations en plus : permutations, opérations arithmétiques complexes, autres. Petit détail : la multiplication est gérée plus simplement et l'on a pas besoin de s’embêter à faire mumuse avec plusieurs instructions différentes pour faire une simple multiplication comme avec le MMX.
On peut quand même signaler une chose : des instructions permettant de contrôler le cache firent leur apparition. On retrouve ainsi des instructions qui permettent d'écrire ou de lire le contenu d'un registre XMM en mémoire sans le copier dans le cache. Ces instructions permettent ainsi de garder le cache propre en évitant de copier inutilement des données dedans. On peut citer par exemple, les instructions MOVNTQ et MOVNTPS du SSE premiére version. On trouve aussi des instructions permettant de charger le contenu d'une portion de mémoire dans le cache, ce qui permet de contrôler son contenu. De telles instructions de prefetch permettent ainsi de charger à l'avance une donnée dont on aura besoin, permettant de supprimer pas mal de cache miss. Le SSE fournissait notamment les instructions PREFETCH0, PREFETCH1, PREFETCH2 et PREFETCHNTA. Autant vous dire qu'utiliser ces instructions peut donner lieu à de sacrés gains si on s'y prend correctement ! Il faut tout de même noter que le SSE n'est pas seul "jeu d'instruction" incorporant des instructions de contrôle du cache : certains jeux d'instruction POWER PC (je pense à l'Altivec) ont aussi cette particularité.
Avec le '''SSE2''', de nouvelles instructions furent ajoutés, permettant d'utiliser des nombres de 64, 16 et 8 bits dans chaque vecteur. Le SSE2 incorporait ainsi pas moins de 144 instructions différentes. Ce qui commençait à faire beaucoup.
Puis, vient le '''SSE3''', avec ses 13 instructions supplémentaires. Pas grand-chose à signaler, si ce n'est que des instructions permettant d'additionner ou de soustraire tous les éléments d'un paquet SSE ensemble, des instructions pour les nombres complexes, et plus intéressant : les deux instructions MWAIT et MONITOR qui permettent de paralléliser plus facilement des programmes.
Le '''SSE4''' fut un peu plus complexe et fut décliné lui-même en 2 versions. Le SSE4.1 introduit ainsi des opérations de calcul de moyenne, de copie conditionnelle de registre (un registre est copié dans un autre si le résultat d'une opération de comparaison précédente est vrai), de calcul de produits scalaire, de calcul du minimum ou du maximum de deux entiers, des calculs d'arrondis, et quelques autres. Avec le SSE4.2, le vice à été poussé jusqu'à incorporer des instructions de traitement de chaines de caractères.
[[File:AVX registers.svg|vignette|Registres AVX.]]
Avec l''''AVX (Advanced Vector eXtensions)''', on retrouve 16 registres d'une taille de 256 bits, nommés de YMM0 à YMM15 et dédiés aux instructions AVX. Ils sont partagés avec les registres XMM : les 128 bits de poids faible des registres YMM ne sont autres que les registres XMM. L'AVX complète le SSE et ses extensions, en rajoutant quelques instructions, et surtout en permettant de traiter des données de 256 bits.
Son principal atout face au SSE est que les instructions AVX permettent de préciser le registre de destination en plus des registres d'opérandes. Avec le SSE et le MMX, le résultat d'une instruction SIMD était écrit dans un des deux registres d'opérande manipulé par l'instruction. Il fallait sauvegarder son contenu si on en avait besoin plus tard, ce qui n'est plus nécessaire avec l'AVX.
==La performance des processeurs SIMD==
Il est intéressant de comparer la performance d'un processeur SIMD et celle d'un processeur normal, sans SIMD. Mais cet exercice est compliqué par le fait que les processeurs non-SIMD sont très nombreux : entre les CPU superscalaires, à émission dans l'ordre, exécution dans le désordre, ceux sans rien de tout cela, on a de quoi être perdu. Dans ce qui suit, nous allons comparer deux processeurs basiques sans pipeline : un avec SIMD, un autre sans.
Comparé à un processeur sans pipeline, on s'attend à ce que la performance soit augmentée d'un facteur N, avec N le nombre maximal d'entiers/flottants que l'on peut mettre dans un vecteur. Si une architecture SIMD fait N calculs en parallèles, alors les performances sont censées être multipliées par N. Cela laisse penser que plus les vecteurs sont longs, plus le gain en performance est important. Il s'agit là d'un résultat basique, mais la vraie vie est différente.
Un premier problème est que ce résultat ne tient que si la mémoire RAM et les caches suivent. En effet, faire N calculs en parallèle demande de lire/écrire N fois plus de données. Les données sont souvent stockées dans des registres vectoriels, ce qui fait que la pression sur le banc de registre est assez importante. Mais cela signifie aussi que les instructions d'accès mémoire doivent lire/écrire N fois plus de données. Il faut alors ajouter des ports sur le cache, élargir le bus mémoire, augmenter la taille des lignes de cache, etc. Le débit binaire des caches et de la mémoire RAM deviennent rapidement des points limitants, qui réduisent le gain en performance des instructions SIMD.
Et ne parlons pas de l'interaction avec la mémoire virtuelle : vu qu'on lit/écrit par paquets de données, cela signifie qu'on traverse une page mémoire plus vite, ce qui fait que les défauts de page sont lus fréquents. Un processeur SIMD a intérêt à être combiné à une RAM et des caches solides, très performants. C'est le cas sur les processeurs modernes, mais cela a pose des problèmes pour les processeurs vectoriels, et a mené à leur abandon progressif.
Mais passons outre ce problème et regardons la seconde raison qui font que ce résultat est naïf : la loi d'Amdhal ! Toutes les portions d'un programme ne sont pas accélérées par des instructions SIMD, il y a des portions dont les calculs ont des dépendances de données, d'autres qui ne sont pas parallélisables, etc. Une partie du programme est donc impossible à véctoriser (i.e optimiser pour le SIMD), une l'autre l'est. Pour simplifier les explications, on suppose qu'une instruction SIMD prend le même nombre de cycles que son équivalent non-SIMD. Par exemple, une addition SIMD prend le même temps qu'une addition normale. Dans ce cas, la portion non-vectorisable du code reste inchangée, alors que la portion vectorisable est divisée par N, par la largeur du vecteur. On retombe sur une formulation identique à la loi d'Amdhal sur le fond.
Une manière de quantifier le tout est d'utiliser la métrique de l'efficience SIMD. Elle est calculée en divisant deux grandeurs. La première est elle-même un rapport : c'est le gain obtenu en terme de nombre d'instructions exécutées. Prenons un programme qui exécute I instructions avant vectorisation, et qui en exécute I/X après. Le gain s'exprime comme suit :
: <math>G = \frac{I}{I/X} = X</math>
Maintenant, prenons ce gain est divisons-le par la largeur d'un vecteur. On obtient alors l'efficience SIMD :
: <math>\text{Efficience SIMD} = \frac{G}{N} = \frac{\frac{I}{I/X}}{N}</math>
Notons que le gain et la largeur SIMD ne sont égales que dans un cas bien précis : tout le code est vectorisable. Il s'agit donc d'une reformulation du gain de la loi d'Amdhal, mais dans laquelle le pourcentage de code série est caché.
Il est intéressant de regarder l'efficience SIMD quand on augmente la portion du code série, ainsi que la taille des vecteurs. Prenons un cas assez généreux, réaliste pour certaines applications très adaptées au SIMD : 1% du code est non-vectorisable, 99% profite du SIMD. L'efficience SIMD varie alors assez rapidement avec la taille des vecteurs. De 99% pour N=2, elle descend à 98% pour N=16, et tombe sous les 50% pou N=128. La conséquence est que les très grandes tailles de vecteurs ne sont pas vraiment utiles, le rendement est décroissant avec la taille des vecteurs.
==Les processeurs SIMD à registres généraux==
Le premier type de processeur SIMD que nous allons voir est celui des processeurs de type '''SWAR''' (''SIMD Within A Register''). Le terme SWAR est un terme polysémique dont le sens a beaucoup changé au fil du temps. Ici, nous allons l'utiliser dans la définition la plus stricte : celle où on effectue du SIMD dans les registres généraux du processeur, sans registres spécialisés. Il s'agit d'une forme de SIMD qui est aujourd'hui peu utilisée, mais qui a été la première forme de SIMD utilisée dans des processeurs grand public commerciaux.
L'idée est d'améliorer un petit peu un processeur normal, non-SIMD. Un processeur usuel contient des registres généraux de 32 à 64 bits, qui stockent chacun un opérande de même taille que le registre. D'anciens processeurs avaient des instructions pour effectuer des calculs simples, sur des opérandes plus courts. Par exemple, les processeurs x86 32 bits sont capables de faire des calculs sur 8, 16, ou 32 bits. Mais on ne pouvait placer qu'un seul opérande de 8, 16 ou 32 bits dans un registre général, ce qui est un léger gâchis. Le SWAR est une amélioration de cette technique qui vise à mieux utiliser les registres et l'ALU.
L'idée est de regrouper plusieurs opérandes de 8, 16, voire 32 bits, dans un seul registre général. De plus, on ajoute des instructions SIMD d'addition/soustraction/autres, qui lisent des opérandes 8/16/32 bits dans ces registres généraux. Par exemple, sur un processeur 32 bits, on peut ajouter une opération d'addition qui lit deux registres de 32 bits, pour récupérer quatre opérandes de 16 bits (deux par registre) et effectue deux additions 16 bits en même temps dans l'ALU. Et il y a la même chose pour d'autres opérations, comme la soustraction, la comparaison, etc.
Les instructions SIMD disponibles sur ces processeurs se limitent généralement à des additions, soustractions, comparaisons, mais guère plus. De plus, il s'agit d'instructions entières, il n'y a pas d'instructions SIMD flottantes. La raison à cela est très simple, mais nous l'expliquerons plus bas, dans la section sur l'implémentation. Disons simplement que de tells architectures visent une économie en circuit maximale. Elles implémentent les instructions SIMD en utilisant le moins de circuits possibles, ce qui fait qu'elles réutilisent les registres généraux et n'ont pas de registres SIMD séparés.
===Les extensions/jeux d'instructions de type SWAR===
Les premiers processeurs à intégrer du SWAR étaient les processeurs DEC Alpha, avec l'extension multimédia '''''Motion Video Extensions'''''. Les processeurs en question étaient les processeurs Alpha 21164PC (PCA56 and PCA57), Alpha 21264 (EV6) and Alpha 21364 (EV7). Les instructions SIMD disponibles étaient très simples et se résumaient à quelques comparaisons : trouver le maximum ou le minimum de deux opérandes de 8/16 bits, quelques instructions de permutation.
Les extensions '''''Multimedia Acceleration eXtensions''''' des processeurs Hewlett-Packard PA-RISC sont aussi de ce type, mais disposent de plus d'instructions SIMD. La première version, appelée MAX-1, était disponible sur les processeurs 32 bits de la marque, à partir du processeur PA-7100LC. Les instructions disponibles étaient des additions et soustractions d'opérandes 16 bit. Il y avait en tout trois instructions d'addition et trois pour la soustraction. En tout, il y avait :
* une instruction d'addition/soustraction non-signée utilisant l'arithmétique modulaire ;
* une instruction d'addition/soustraction signée en arithmétique modulaire ;
* une instruction d'addition/soustraction signée en arithmétique saturée.
Outre les additions/soustractions, il y avait aussi une instruction pour calculer la moyenne de deux opérandes 16 bits, ainsi qu'une addition fusionnée avec un décalage.
La seconde version, appelée MAX-2, ajouta des instructions d'additions fusionnées avec des décalages, mais aussi des instructions de permutation. De plus, cette version était disponible sur les processeurs 64 bits de la marque, ce qui fait que les registres étaient eux aussi de 64 bits. Ils pouvaient contenir deux fois plus d'opérandes 16 bits. Il n'y avait de gestion d'opérandes 32 bits pour les extensions SIMD.
===L'implémentation matérielle===
L'avantage de cette technique est la grande simplicité d'implémentation. On n'ajoute pas de registres SIMD séparés, ce qui a de nombreux avantages : économie de circuits car pas besoin d'un second banc de registre, pas besoin de sauvegarder des registres SIMD en plus lors d'un appel système/interruption, etc. L'implémentation a juste besoin de modifier le décodeur et l'unité de calcul. Le décodeur est modifié pour ajouter des instructions en plus, rien de spécifique au SWAR. Les modifications de l'unité de calcul sont spécifiques au SWAR et modifient la manière dont elle gère les retenues.
Une première solution est d'utiliser une ALU bit-slicée, ce qui est l'idéal pour les unités de calcul entières. Par exemple, pour une ALU 32 bits entière, on peut la découper en 4 unités de calcul 8 bits. Pour une opération SIMD avec 4 opérandes 8 bits, les quatre ALU fonctionnent en parallèle, il n'y a pas transmission des retenues. Pour les calculs SIMD avec des opérandes de 16 bits, on regroupe les ALU par paire et on propage les retenues à l'intérieur d'une paire, mais pas entre les paires. Enfin, pour un calcul non-SIMD, les opérandes de 32 bits, on propage les retenues normalement, d'une ALU 8 bits vers la suivante.
Une autre solution prend un additionneur 32/64 bits normal, mais mets à 0 les retenues. L'implémentation est très simple avec un additionneur à anticipation de retenues, où les retenues sont calculées en avance, avant de faire l'addition proprement dite, par un circuit d'anticipation de retenue. Il suffit alors d'ajouter un circuit de masquage en sortie du circuit d'anticipation de retenue, qui met à zéro les retenues adéquates. La gestion des débordements demande d'ajouter des circuits, ou du moins de modifier ceux déjà présents dans l'ALU. Typiquement, les circuits pour gérer l'arithmétique saturée sont un peu modifiés pour effectuer la mise à 1111...111 octet par octet et chaque octet est masqué si besoin.
[[File:ALU modifiée pour implémenter du SWAR.png|centre|vignette|upright=3|ALU modifiée pour implémenter du SWAR]]
Notons que la technique ne s'applique qu'aux additions, et aux opérations dérivées comme la soustraction et les comparaisons (ces dernières sont des soustractions déguisées). Mais les multiplications ou divisions ne peuvent pas s'implémenter simplement en modifiant des ALU existantes, du moins pas avec des modifications simples. Aussi, les processeurs qui utilisent cette technique se bornent aux additions et opérations dérivées, pas plus.
La gestion des opérations de permutation est quant à elle très simple : beaucoup de processeurs disposent déjà d'instructions de permutation d'octets, qui sont l'équivalent d'instructions SIMD de permutation pour les registres généraux. Nous en avions déjà parlé dans le chapitre sur le langage machine et l'assembleur, quand nous avions fait la liste des instructions les plus courantes. De telles instructions de permutation d'octet sont utiles pour gérer le boutisme ou effectuer quelques manipulations assez rares. Pas besoin de rajouter une ALU dédiée, celle-ci est déjà présente de base, du moins si les instructions de permutation d'octet sont déjà présentes.
==Les processeurs SIMD purs (''packed SIMD'')==
Maintenant que nous avons vu les instructions SIMD, passons maintenant aux processeurs eux-même. Tous les processeurs que nous allons voir dans ce qui suit supportent des instructions SIMD. Mais leur implémentation matérielle, leur micro-architecture, n'est pas la même. Ils ont aussi quelques différences en termes de jeu d'instruction, mais qui sont fortement liées à l'implémentation matérielle.
Nous allons ici voir les '''processeurs SIMD purs''', aussi dits de type ''Packed SIMD''. Ils implémentent des instructions SIMD sans rien de plus, juste le strict minimum : pas de prédication, pas de vecteurs de taille variable, presque pas d’instructions horizontales, architecture de type LOAD-STORE. De tels processeurs utilisent plusieurs ALU entières/flottantes qui travaillent en parallèle. De plus, ils disposent de registres SIMD séparés des registres généraux. Voyons ces points immédiatement.
* La première distinction est que les vecteurs sont stockés dans des registres séparés des registres généraux, appelés les '''registres SIMD''', ou ''registres vectoriels''. Ils sont beaucoup plus larges que les registres classiques et mémorisent 2, 4, 8 entiers/flottants.
* La seconde distinction est que les calculs SIMD sont effectués dans une unité SIMD séparée de l'ALU ou de la FPU. L'unité de calcul SIMD peut exécuter au minimum des instructions SIMD verticales, plus rarement des instructions SIMD horizontales. Pour ce faire, elle regroupe plusieurs additionneurs/multiplieurs, qui effectuent des calculs en parallèle.
===Les instructions SIMD verticales : plusieurs ALU travaillant en parallèle===
[[File:SIMD2.svg|vignette|Parallélisme de données au niveau de l'unité de calcul. Celle-ci contient plusieurs circuits indépendants qui appliquent la même opération sur des données différentes.]]
Une unité de calcul SIMD gère au minimum des instructions SIMD verticales. Et pour ce faire, elle contient plusieurs additionneurs/multiplieurs séparés. Par exemple, pour additionner deux vecteurs contenant chacun 16 entiers/flottants, il faut utilise 16 additionneurs entiers et 16 additionneurs flottants. Dans le cas général, une ALU SIMD est composée de plusieurs ALU entières et flottantes regroupées ensemble et avec quelques circuits pour gérer les débordements et d'autres situations. Notons que les ALU travaillent en parallèle, elles font des calculs indépendants.
Notons que les additionneurs dans chaque ALU doivent pouvoir être configurés de manière à gérer des tailles de données différentes. Par exemple, si on prend un vecteur simple, qui peut contenir soit 32 entiers de 16 bits, soit 16 entiers 32 bits, soit 8 entiers 64 bits, alors on doit utiliser 8 additionneurs, mais chacun d'entre eux doit pouvoir être reconfiguré de manière à ne pas propager les retenues au-delà des premiers 16 ou 32 bits.
Un défaut de cette organisation est que le cout en circuits est loin d'être négligeable. Il faut dupliquer des unités de calcul et les coller en rajoutant des circuits, cela utilise beaucoup de transistors. Le cout en circuit est d'autant plus grand que les vecteurs sont longs et le cout est approximativement proportionnel à la taille des vecteurs. Entre des vecteurs de 128 et 256 bits, l'unité de calcul utilisera globalement deux fois plus de circuits avec 256 bits qu'avec 128. Même chose pour les registres, mais c'est là un cout commun à toutes les architectures.
Retenez cependant : l'usage de plusieurs ALU travaillant en parallèle a plusieurs défauts. Premièrement, cela implique des vecteurs de taille fixe, du fait que le nombre d'ALU travaillant en parallèle est fixe. Deuxièmement, des difficultés d'implémentation concernant les instructions de réduction. Détaillons ce second point !
Une autre possibilité est d'utiliser moins d'unités de calcul qu'il n'y a d'éléments dans un vecteur. Par exemple, pour des vecteurs contenant 32 flottants, il se peut qu'il n'y ait que 16 ALU. Les opérations sur les vecteurs sont donc faites en deux fois : une première passe pour les 16 premiers éléments, une seconde passe pour les 16 restants. On économise ainsi pas mal de circuits, mais cela se fait au détriment de la performance globale. L'avantage est que l'ensemble des calculs se fait en une seule instruction machine. L'implémentation est généralement la suivante : le processeur décode une instruction SIMD en deux micro-instructions SIMD plus courtes. Par exemple, pour une instruction SIMD de 32 éléments, elle sera décodée en deux instructions SIMD de 16 éléments, chacune étant exécutée sur l'ALU.
Il est possible de réduire fortement l'impact en performance en doublant la fréquence de l'ALU. Par exemple, pour des vecteurs contenant 32 flottants, le processeur incorpore 16 ALU, mais celles-ci fonctionne à une fréquence double de celle du processeur. L'usage d'une ALU à double fréquence est une technique qui a été utilisée sur le Pentium 4 et quelques processeurs, pour des instructions non-SIMD. Mais pour les processeurs SIMD, elle s'applique à la perfection. L'implémentation est la même que précédemment, sauf que la fenêtre d'instruction et la logique d'émission fonctionnent à double fréquence. Pour limiter la casse, il est préférable d'utiliser une fenêtre d'instruction séparée pour les instructions SIMD, qui sera seule à fonctionner à double fréquence.
===Les instructions horizontales : une ALU simple séparée===
Les instructions horizontales posent des difficultés d’implémentation. Elles ne peuvent pas s'implémenter en utilisant plusieurs ALU travaillant en parallèle, contrairement aux instructions verticales, ce qui fait qu'elles utilisent généralement des unités de calcul spécialisées. Et c'est là que la différence entre instructions de manipulation de vecteurs et instructions de réduction vient encore une fois poser problème. Les instructions de manipulation de vecteur sont relativement simples à implémenter : elles ont juste besoin d'une ALU spécialisée, relativement simple, composée de beaucoup de multiplexeurs à configurer convenablement. Mais pour les instructions de réduction, c'est autre chose !
L'implémentation des instructions de réduction est possible mais a un cout en circuits assez prohibitif. Pour additionner N entiers, il faut utiliser un additionneur multi-opérande qui prend beaucoup de circuits et dont le temps de calcul est proche de celui d'une multiplication (très lent). Trouver le maximum et le minimum d'un nombre demandent de faire la même chose avec des comparateurs. Le tout a un cout en circuit non-négligeable, pour accélérer des opérations assez peu fréquentes. Elles sont surtout utilisées pour trouver la somme ou le maximum/minimum d'un tableau, opération séquentielle par nature, avec peu parallélisme de données, peu courante.
Une autre solution utilise une unité SIMD normale, mais intègre un système de contournement, de ''bypass'', qui relierait l'entrée d'une ALU à la sortie d'une autre. Mais le cout lié aux interconnexions est très important : on doit relier chaque ALU à toutes les autres dans le pire des cas, on peut optimiser le tout et réduire un peu la quantité d'interconnexions, mais le cout reste important. Au final, on gagne en circuits ce qu'on perd en interconnexions. Le choix entre les deux solutions est loin d'être facile et dépend du processeur, de la technologie utilisée, du budget en transistors disponibles, etc.
Les processeurs SIMD purs résolvent ce problème assez simplement : ils n'implémentent pas d'instructions de réduction. Par contre, ils implémentent systématiquement des instructions de manipulation de vecteur, comme des permutations ou autres. La raison est que l'on peut émuler les instructions de réduction en utilisant des instructions SIMD de manipulation de vecteur couplées à des additions/multiplications SIMD verticales. Il s'agit donc d'un compromis entre cout en circuits et performance finale. Niveau circuits, on a une unité SIMD avec plusieurs ALU de calcul en parallèle, une unité de manipulation de vecteur séparée assez simple et au cout en circuits raisonnable. Les performances pour les opérations de réduction sont acceptables, le cout en performance est relativement modéré.
===Le banc de registre vectoriel===
La présence de registres vectoriels séparés permet d'avoir des vecteurs assez longs. Pour ce faire, les registres vectoriels sont plus grands que les registres généraux. Par exemple, sur un processeur 32 bits, qui gère donc des entiers de 32 bits, les vecteurs peuvent faire 128 ou 256 bits. Un vecteur contient donc plusieurs entiers ou flottants de taille maximale. On n'est pas dans le cas précédent, où registres généraux et SIMD sont les mêmes, ils sont séparés et n'ont pas la même taille.
Le processeur utilise un banc de registre séparé pour les registres SIMD, appelé le '''banc de registre SIMD'''. Vu que les registres vectoriels sont assez longs, le banc de registre vectoriels a une grande taille, une grande capacité mémoire. Pour donner un exemple, les extensions SSE des processeurs x86 définissent 8 registres SIMD de 128 bits chacun. Ils ont été introduits à une époque où les processeurs avaient des registres généraux de 32 bits. Le banc de registres associé était donc environ 4 fois plus grand que celui pour les registres généraux. Je dis environ car le décodeur avait sensiblement la même taille, seul le plan mémoire était grossit, mais l'idée est là.
Une conséquence est que le banc de registres SIMD consomme beaucoup d'énergie et d'électricité. Il en est de même avec les ALU SIMD. Et diverses optimisations tentent de réduire ce cout en énergie. L'une d'entre ajoute une mémoire cache entre les ALU SIMD et les registres vectoriels ! L'idée est que si un registre vectoriel est relu plusieurs fois de suite, la première lecture lira le vecteur dans le banc de registres SIMD, mais les lectures suivantes liront le vecteur dans le cache. L'optimisation marche car accéder au banc de registre vectoriel est vraiment couteux, plus qu'accéder à un cache. Le cache en question est appelé un '''''register reuse caches'''''.
Les ''register reuse caches'' sont présents sur le processeur Monaka de Fujitsu, mais aussi et surtout dans les cartes graphiques modernes. Nous ferrons l'impasse sur les GPU modernes, car leur usage est quelque peu particulier. Il ne servent pas que à réduire la consommation d'énergie, mais aussi à réduire les conflits d'accès au banc de registre, qui est en réalité composé de plusieurs banques indépendantes. Leur usage sur le CPU Monaka est plus simple : ils ne servent vraiment qu'à réduire la consommation d'énergie, rien de plus.
: Pour ceux qui veulent en savoir plus pour leur utilisation sur les cartes graphiques, tout est détaillé dans mon wikilivre sur les cartes graphiques, via le lien suivant. Mais sachez cependant qu'il faut idéalement lire le chapitre entier, et peut-être même les chapitres précédents, pour comprendre cette section. Voici le lien : [https://fr.wikibooks.org/wiki/Les_cartes_graphiques/La_microarchitecture_des_processeurs_de_shaders#L'Operand_Collector_et_les_caches_de_register_reuse L'Operand_Collector_et_les_caches_de_register_reuse]
===L'implémentation des LOAD-STORE à des données consécutives===
Les architectures SIMD pures sont des architectures de type LOAD-STORE, ce qui veut dire que les instructions SIMD ne peuvent que lire ou écrire dans les registres vectoriels, pas en mémoire ou ailleurs. Les transferts entre registres SIMD et mémoire sont le fait d'instructions LOAD et STORE spécialisées, qui transfèrent des vecteurs entre RAM et registres vectoriels. Les autres architectures n'ont pas nécessairement ce genre de restrictions, mais laissons cela pour plus tard.
Implémenter les instructions LOAD/STORE pour des vecteurs dont les données sont consécutives en mémoire n'est pas très compliqué. Il suffit juste d'élargir le port de lecture/écriture du cache L1 de données, pour pouvoir lire/écrire un vecteur entier. Rien de bien compliqué, il suffit juste d'ajouter des interconnexions et d'ajouter des multiplexeurs. Du moins, c'est le cas tant que les vecteurs sont plus petits qu'une ligne de cache, ce qui est souvent le cas en pratique. Dans le cas très rare où les vecteurs sont plus longs qu'une ligne de cache, les LOAD/STORE doivent se faire en deux accès dans le cache, ce qui demande d'ajouter du matériel pour contrôler la situation.
Précisons cependant qu'il s'agit là d'un cas idéal, où les vecteurs sont correctement alignés en mémoire. En effet, il se peut qu'un vecteur soit à cheval sur deux lignes de cache, alors qu'il est plus petit qu'une ligne de cache. Il suffit pour cela qu'il soit placé à une adresse adéquate. Par exemple, prenons un vecteur de 16 octets et des lignes de cache de 32 octets. Si le vecteur démarre à l'adresse 24, alors il sera à cheval sur deux lignes de cache. Il faut donc intégrer des circuits pour gérer cette situation. Pour cela, il suffit d'ajouter un registre pour stocker la première de ligne lue, puis un circuit qui combine les deux lignes de cache pour donner le vecteur final.
===L'implémentation des LOAD-STORE en ''scatter-gather''===
L'implémentation des accès en ''scatter-gather'' est plus compliquée. Un accès en ''scatter-gather'' demande de faire des calculs d'adresse, suivis par un ensemble de lectures/écritures, suivis par un regroupement des données lues dans un vecteur. Le calcul d'adresse peut se faire en parallèle dans une unité de calcul SIMD, dans plusieurs unités de calcul d'adresse en parallèle. Par contre, les lectures/écritures ne le peuvent pas. Le cache n'a pas assez de ports de lecture/écriture pour lire/écrire N données. Il a quelques ports, ce qui lui permet de lire/écrire 3/4 données grand maximum, soit moins que ce qui est nécessaire pour faire un accès en ''scatter-gather'' en une fois. Les lectures/écritures sont donc effectuées en plusieurs fois. Pour une opération de ''Gather'', il faut aussi regrouper les données lues dans un vecteur.
Sur les caches des processeurs à haute performance, le calcul d'adresse est intégré dans le décodeur. Ce sont des caches adressés par somme, que nous avions abordé dans le chapitre sur les mémoires caches. Avec de telles caches, on ne peut pas utiliser une unité de calcul d'adresse SIMD, on ne peut calculer qu'autant d'adresse qu'il y a de ports sur le cache.
Les accès en ''scatter-gather'' regroupent plusieurs accès mémoire, qui peuvent être effectués dans le désordre. La seule exception est celle où un ''scatter'' effectue plusieurs écritures à la même adresse, où les deux écritures doivent se faire dans l'ordre allant du premier au dernier élément du vecteur d'adresse. Les processeurs SIMD profitent de cette absence d'ordre pour effectuer les accès dans un ordre le plus optimisé possible.
Par exemple, imaginons le cas où une instruction ''gather'' lise des éléments placés dans deux lignes de cache uniquement, mais dans le désordre en passant sans cesse d'une ligne de cache à l'autre. Il est alors possible de faire tous les accès dans la première ligne de cache en premier, avant de faire ceux allant dans l'autre. Une telle optimisation marche aussi bien pour les lectures que les écritures, pour les ''gather'' que pour les ''scatter''. Elle peut aussi être adaptée pour plus de deux lignes de cache. Elle porte le nom de '''''memory coalescing'''''.
Une autre optimisation possible est de détecter le cas où un accès en ''scatter-gather'' accède à des données consécutives. Il suffit pour cela que les indices/adresses du vecteur opérande soient consécutives, et cela arrive plus souvent qu'on ne le pense. Dans ce cas, l'accès est remplacé par une instruction LOAD/STORE SIMD normale, beaucoup plus simple et plus rapide. Les deux optimisations demandent que les adresses soient calculées avant de faire l'accès mémoire. Les adresses calculées sont alors comparées entre elles, par un réseau de comparateurs assez complexe.
Un point important de l'implémentation des instructions ''gather'' est qu'une donnée chargée doit être insérée au bon endroit dans le vecteur destination. Et pareil pour les ''scatter'' : il faut lire la bonne donnée au bon moment. Si on prend une ligne de cache complète, il faut lire plusieurs élèments en même temps et les envoyer au registre de destination au bon endroit. Il faut pour cela tout un réseau de multiplexeurs assez complexe pour faire ce travail. Il y a le même genre de réseau pour les instructions ''scatter'', mais qui va dans l'autre sens : du registre vers la ligne de cache.
==Les processeurs SIMD avec prédication==
Un obstacle très gênant à la vectorisation est la présence de branchements conditionnels dans les boucles à vectoriser. Si une boucle contient des branchements conditionnels, elle ne peut pas être vectorisée facilement : il est impossible de zapper certains éléments d'un vecteur suivant une condition. Il n'est pas possible d'effectuer une instruction SIMD seulement sur certains éléments d'un vecteur et d'ignorer les autres, en fonction du résultat d'un branchement conditionnel. Tout le vecteur y passe, ou le vecteur est épargné, mais la condition intermédiaire n'est pas possible avec du SIMD simple.
Pour résoudre ce problème, les processeurs SIMD incorporent diverses techniques pour implémenter ou éviter les branchements conditionnels le plus possible. La plus simple est l'incorporation de certaines instructions SIMD simples, comme la valeur absolue, le calcul du minimum/maximum, etc. Ces opérations sont généralement émulées en utilisant des branchements, avec quelques conditions simples. Incorporer ces instructions permet ainsi de faire disparaitre une partie des branchements du code. Elle ne paye pas de mine, mais elle a un résultat pas négligeable pour le rendu graphique, certaines applications de calcul scientifiques, et quelques autres. Son cout en transistors est très variable, il faut rajouter des circuits dans le séquenceur, l'unité de calcul SIMD est assez peu modifiée (il faut juste rajouter des multiplexeurs).
Le vrai problème est que cette solution laisse énormément de branchements dans le code. Et qu'il faut donc trouver une solution plus générale, capable de réduire drastiquement les branchements. Pour cela, on réutilise une technique vue dans les chapitre sur l'assembleur et le langage machine : la prédication.
===Les instructions à prédicats===
Pour optimiser le cas général, il est possible d'utiliser des instructions à prédicats adaptées pour fonctionner sur des vecteurs. L'idée est que quand une instruction effectue un calcul sur un ou deux vecteurs, certains éléments du vecteurs sont ignorés. Les éléments à ignorer sont choisis suivant le résultat d'une instruction de comparaison, qui effectue un test : les éléments pour lesquels ce test est respecté sont pris en compte, ceux qui ne passent pas le test sont ignorés.
Pour donner un exemple d'utilisation, imaginons que l'on ait un vecteur dans lequel on veut remplacer toutes les valeurs négatives par des 0. Dans ce cas, on utilise :
* une instruction de comparaison, qui compare chaque élément du vecteur avec 0 et génère plusieurs bits de résultat ;
* suivi d'une instruction à prédicat qui met à zéro les éléments pour lesquels les bits de résultat précédents sont à 1.
La prédication peut aussi être utilisée pour simuler des vecteurs de taille variable. Tous les vecteurs ont une taille fixe, mais on peut ne pas faire de calculs sur la fin d'un vecteur, ce qui fait que tout se passe comme si le vecteur était plus petit. Ce n'est pas sa seule utilisation, mais la prédication permet de simuler ce comportement. Néanmoins, cela n'en fait pas un processeur vectoriel, capable de traiter des vecteurs de taille variable : il n'y a pas de moyen de préciser explicitement la taille des vecteurs à traiter.
Tout cela demande plusieurs modifications du jeu d'instruction : ajouter des instructions de comparaison SIMD, ajouter de quoi faire la prédication.
===Le calcul des masques et le registre de masque===
La prédication demande que le résultat d'une comparaison soit stocké dans un registre à prédicat ou dans le registre d'état. Pour les processeurs SIMD, il y a cependant une grosse adaptation à faire concernant le fonctionnement des comparaisons et le stockage des résultats.
Premièrement, les instructions de comparaison SIMD comparent une paire de deux éléments à la même place dans deux vecteurs et fournissent un résultat d'un bit pour chaque paire. Le résultat est donc un ensemble de bits, qui sont regroupées dans un vecteur spécialisé.
Deuxièmement, reste à savoir que faire de ce vecteur. Pour cela, deux solutions sont possibles. Avec la première, le vecteur est stocké dans un registre vectoriel/SIMD comme les autres, il est traité comme n'importe quel autre vecteur. Avec la seconde solution, il reçoit un registre dédié, qui est un mix entre registre d'état et registre à prédicat, appelé un '''registre de masque''' (''Vector Mask Register''). Il stocke un bit pour chaque donnée présente dans le vecteur à traiter, qui indique s'il faut ignorer la donnée ou non. L'instruction SIMD suivante fait usage de ce masque pour savoir s'il faut faire l'opération pour chaque élément du vecteur.
[[File:Vector mask register.png|centre|vignette|upright=2|Vector mask register]]
Il peut y avoir un ou plusieurs registres de masque. S'il y en a plusieurs, ils sont généralement nommés, sur le modèle des registres à prédicats. L'application d'un masque demande de fournir le nom du registre de masque à utiliser, ils adressés explicitement dans les instructions. S'il n'y en a qu'un seul, il est possible de le pas le nommer et de faire en sorte qu'il soit adressé implicitement par les instructions qui en ont besoin. On parle alors de '''masquage implicite'''. Le masquage implicite est très utilisé sur les cartes graphiques, qui sont actuellement des architectures SIMD, comme nous le verrons plus bas.
Un gros problème du masquage implicite est qu'il introduit des dépendances implicites entre instructions, qui ne sont pas explicites quand on regarder uniquement les registres manipulés par une instruction, qui perturbent le renommage de registres. Rien d'insurmontable, mais cela peut poser des problèmes de performance et la résolution de ce problème a un cout en hardware. Mais ce n'est un problème que sur les architectures à exécution dans le désordre. Les architectures à émission dans l'ordre ne sont pas concernées, et c'est notamment le cas sur les GPU et les cartes graphiques modernes.
Diverses optimisations sont possible avec un registre de masque. La première est de ne pas exécuter d'instruction si tous les bits du masque sont à 0. Dans ce cas, l'instruction SIMD n'a pas de calculs à faire, vu que tous les résultats sont masqués. Il est alors possible de ne pas exécuter l'instruction SIMD et de passer directement à la suivante. L'implémentation matérielle est très simple : une porte NOR qui détermine si le registre de masque contient la valeur 0. La sortie de cette porte NOR est envoyée au séquenceur d'instruction. Le décodeur est alors conçu pour zapper l'instruction si le registre de masque contient un zéro.
===Les accès mémoire en ''scatter-gather'' avec prédication===
Un problème avec les accès mémoire que certains accès peuvent déclencher des défauts de page ou d'autres formes d'exceptions (erreurs de protection mémoire, autre). Si l'accès mémoire se fait sans prédication, ces exceptions sont gérées dans l'ordre des accès mémoire, en commençant par le début du vecteur, ce qui rend l'implémentation assez facile. Mais avec la prédiction, il se peut qu'un accès masqué et censé être ignoré déclenche une exception matérielle. Et le résultat dépend de l'implémentation.
Une première solution est d'effectuer l'accès mémoire, et de masquer ensuite les éléments à ignorer. Mais dans ce cas, les éléments masqués peuvent déclencher des exceptions, qui ne sont pas censées se produire. Une autre solution est de déterminer quelles sont les adresses qu'il faut lire ou écrire et ne pas faire les accès mémoire masqués. Une autre solution effectue tous les accès avant de savoir quels sont ceux à masquer, mais de ne pas déclencher d'exception avant qu'on sache quels sont les accès masqués. Une fois le masquage des accès effectués, le processeur exécute les exceptions adéquates. Il s'agit d'un comportement de masquage d'exception, très utilisé.
L'implémentation est assez simple pour les accès mémoire contiguës, mais plus compliqué pour les accès en ''stride'' ou en ''scatter-gather''. Pour les accès contiguës, les seules exceptions pouvant survenir sont celles ayant lieu quand on passe d'une page mémoire à la suivante (au sens pagination, mémoire virtuelle). Et le hardware pour détecter un débordement de page est assez simple. Mais pour les accès en ''stride'' ou en ''scatter-gather'' demandent qu'oin vérifie les exceptions pour chaque élément du vecteur.
===L'implémentation matérielle===
L'implémentation matérielle est assez proche des processeurs SIMD purs. On retrouve plusieurs unités de calcul travaillant en parallèle, avec quelques circuits ajoutés pour gérer la prédication. Le cout supplémentaire en circuits est acceptable, les gains en performance associés sont très intéressants. Les circuits en plus, pour gérer la prédication, sont similaires à ceux utilisés pour la prédication sur les architectures non-SIMD, mais sont dupliqués.
La seule innovation architecturale est l'implémentation du registre de masque. Tous les processeurs n'en utilisent pas, mais c'est la solution la plus performante. En effet, ce registre de masque est un peu l'équivalent du registre d'état pour les instructions SIMD, bien qu'il soit techniquement une concaténation de registres à prédicat non-nommés. Ils ont pour avantage de libérer des registres SIMD normaux tout en ayant un cout en matériel très limité. Un autre point est que pour gérer les masques, les instructions SIMD à prédicat doivent lire le masque. Sans registre de masque, en utilisant un registre SIMD normal, il faut rajouter un troisième port de lecture sur le banc de registre pour récupérer le masque, ce qui est couteux en matériel. Pas besoin avec un registre de masque, qui n'est connecté qu'à l'ALU, sur le même modèle que le registre d'état.
Plus haut, on a vu que l'implémentation de l'ALU SIMD peut se faire en utilisant des moins d'ALU que prévu. Par exemple, pour des vecteurs de 32 éléments, on peut utiliser 16 ALUs (allant à double fréquence ou non). Une instruction SIMD est alors décodée en plusieurs micro-instructions SIMD consécutives, chacune traitant une partie d'un vecteur. Par exemple, une instruction SIMD sur des vecteurs de 32 éléments est exécutée par deux micro-instructions travaillant sur des vecteurs de 16 éléments.
L'avantage est que cela se marie bien avec l'abandon des opérations pour les masques dont tous les bits sont à 0. Par exemple, prenons une instruction travaillant sur 16 flottants, exécutée en deux fois sur 8 flottants. Si le masque dit que les 8 premières opérations ne sont pas à exécuter, alors l'ALU ne fera que le calcul des 8 derniers flottants. Pour cela, le décodeur doit vérifier que les 8 bits de poids faible ainsi que de poids fort du registre de masque ne soient pas individuellement à zéro. Cela se fait en dupliquant la porte NOR évoquer plus haut, l'une pour la partie basse du registre et la seconde pour la partie haute.
==La prédication pour les structures de contrôle imbriquées==
Au niveau du jeu d’instruction, les architectures SIMT implémentent de la prédication, sous une forme améliorée. Les processeurs SIMT actuels sont surtout utilisées sur les processeurs intégrés aux cartes graphiques. Et ces derniers gèrent très mal les branchements, et encore : beaucoup de cartes graphiques, même récentes, ne gèrent tout simplement pas les branchements. Elles doivent donc se débrouiller avec uniquement la prédication, là où les processeurs SIMD utilisent des branchements normaux en complément de la prédication. Insistons sur le fait que cet usage exclusif de la prédication n'est présent que sur une sous-partie des architectures SIMT, le seul exemple que l'auteur de ce wikilivre connait étant celui des cartes graphiques.
Les architectures SIMT sans branchements doivent donc trouver des solutions pour gérer les structures de contrôle imbriquées, à savoir une boucle placée à l'intérieur d'une autre boucle, un IF...ELSE dans un autre IF...ELSE, etc. Elles utilisent pour cela la prédication, combinée avec des mécanismes annexes. Le premier d'entre eux est l'usage de plusieurs registres de masques organisés d'une manière bien précise, l'autre est l'usage de compteurs d'activité. Voyons ces deux techniques.
===La pile de masques===
La '''pile de masques''' remplace le ou les registres de masque. Sans elle, le processeur SIMD incorpore un registre de masque qui est adressé implicitement ou explicitement. Éventuellement, le processeur peut contenir plusieurs registres de masque séparés adressables via un nom de registre. Avec elle, le processeur SIMD incorpore plusieurs registres de masque organisé en pile. Le registre de masque est donc remplacé par une mémoire LIFO, une pile, dans laquelle plusieurs masques sont empilés.
Le tout forme une pile, similaire à la pile d'appel, sauf qu'elle est utilisée pour empiler des masques. Un masque est calculé et empilé à chaque entrée dans une structure de contrôle, puis dépilé une fois la structure de contrôle exécutée. L'empilement et le dépilement des masques est effectué par des instructions PUSH et POP, présentes dans le jeu d'instruction du processeur SIMD.
Le calcul des masques doit répondre à plusieurs impératifs.
* Premièrement, chaque masque se calcule en faisant un ET entre le masque précédent et le masque calculé par l'instruction de test. Cela permet de ne pas réveiller d’élément au beau milieu d'une structure imbriquée. Si in IF désactive certains éléments du vecteur, une condition imbriquée dans ce IF ne doit pas réveiller cet élément. Le fait de faire un ET entre les masques garantit cela.
* Deuxièmement, les masques doivent être empilés et dépilés correctement. Au moment de rentrer dans une structure de contrôle, on effectue une instruction de test associée à la structure de contrôle, qui calcule un masque, et on empile le masque calculé. Au moment de sortir de la structure de contrôle, on dépile le masque en question.
L'implémentation demande d'utiliser une mémoire LIFO pour stocker la pile de masques, et quelques circuits annexes. Il faut notamment un circuit relié à l'ALU qui récupère les conditions, les résultats des comparaisons, et qui effectue le ET pour combiner les maques.
Pour donner un exemple, prenons le code suivant, qui est volontairement simpliste et ne sert qu'à des fins d'explication :
<syntaxhighlight lang="c">
if ( condition 1 )
{
if ( condition 2 )
{
...
}
else
{
...
}
Autres instructions
}
Instructions après le IF...
</syntaxhighlight>
Imaginons que l'on traite des vecteurs de 8 éléments.
Pour le vecteur considéré, la première condition (a > 0) n'est respectée que par les 4 premiers éléments. L'instruction de condition calcule alors le masque correspondant : 1111 0000. Le masque est alors calculé, puis empilé au somment de la pile.
La seconde instruction de test, qui teste la variable b, est maintenant valide pour les 4 bits du milieu du masque. Mais n'allez pas croire que le masque correspondant soit 0011 11100 : il faut tenir compte de la condition précédente, qui a éliminé les 4 derniers éléments. Pour cela, on fait un ET logique entre le masque précédent, et le masque calculé par la condition. Le masque au sommet de la pile est donc lu, combiné avec le masque calculé par l'instruction, ce qui donne le masque final. Le masque final est alors empilé au sommet de la pile.
On exécute alors l'instruction du IF, en tenant compte du masque qui est au sommet de la pile. Si le IF était plus compliqué, toutes les instructions suivantes tiendraient compte du masque. En fait, le masque est pris en compte tant qu'il n'est pas dépilé. Une fois que le IF est terminé, le masque est dépilé.
On passe alors au ELSE, et rebelotte. Le masque pour le ELSE est calculé en combinant le masque au sommet de la pile avec la condition du ELSE. Le masque au sommet de la pile est celui calculé à l'entrée du premier IF, pas le second qui a été dépilé. Les instructions du ELSE sont alors exécutées en tenant compte de ce masque. Une fois qu'elles sont toutes exécutées, le masque est dépilé.
Puis vient l'exécution des instructions après le ELSE. Elles utilisent le masque empilé au sommet de la pile, qui correspond à celui à l'entrée du IF.
Puis vient le moment d'exécuter les instructions après le IF : pas de masque, on exécute sur tout le vecteur.
===Les compteurs d'activité===
Une variante de la technique précédente remplace la pile de masques par des '''compteurs d'activité'''. La technique est similaire, si ce n'est qu'elle utilise moins de circuits. Avant , on avait une pile de masques de même taille, dont les bits sont à 0 ou 1 suivant que la condition est remplie. La pile de masque ressemble donc à ceci :
{|class="wikitable"
|-
! masque 1
| 1 || 1 || 1 || 1
|-
! masque 2
| 0 || 1 || 1 || 1
|-
! masque 3
| 0 || 1 || 1 || 1
|-
! masque 4
| 0 || 0 || 0 || 1
|-
! masque 1
| colspan="4" | vide
|}
Une manière équivalent de représenter cette pile de masque est de compter combien de bits sont à 0 dans chaque colonne. Attention : j'ai bien dit à 0 ! On obtient alors :
{|class="wikitable"
|-
! masque 1
| 3 || 1 || 1 || 0
|}
Et c'est le principe caché derrière la techniques des compteurs d'activité. Chaque élément dans un vecteur, chaque place, se voit attribuer un compteur. Un compteur non-nul indique qu'il ne faut pas prendre en compte l’élément. Ce n'est qu'une fois que le compteur est nul que l'on effectue des opérations sur l’élément associé du vecteur.
À chaque fois qu'on entre dans une structure de contrôle, on teste une condition sur chaque élément. Si la condition est respectée pour un élément, alors le compteur ne change pas. Mais si la condition n'est pas respectée, alors on incrémente le compteur associé. En sortant de la structure de contrôle, on décrémente le compteur associé. Notons que les compteurs qui n'ont pas été incrémenté en entrant dans la structure de contrôle ne sont pas décrémenté en sortant. En clair, là où on empilait/dépilait un masque, on se contente d'incrémenter/décrémenter un compteur.
Utiliser un compteur en lieu et place d'une colonne entière dans la pile de masque utilise moins de bits. Et c'est sans doute pour cette raison que certaines cartes graphiques, comme les cartes graphiques intégrées d'Intel depuis 2004, utilisent cette technique.
==Les processeurs vectoriels==
Les '''processeurs vectoriels''' sont les ancêtres des processeurs à instruction SIMD. Bien qu'ils soient arrivés en premier, ils incorporent diverses techniques que de simples instructions SIMD n'ont pas forcément. C'est paradoxal, mais c'est ainsi.
La première différence se manifeste au niveau du jeu d'instruction. Les processeurs vectoriels sont capables de traiter des vecteurs de taille variable, alors que les instructions SIMD usuelles ne gèrent que des vecteurs de taille fixe. Par taille variable, on veut dire que le processeur gère nativement des vecteurs d'une taille allant de 1 à la taille maximale d'un vecteur. La taille maximale d'un vecteur est celle permise par la taille des registres vectoriels, il s'agit de la taille d'un vecteur SIMD équivalent sur les autres processeurs SIMD.
La seconde différence est une différence en termes de micro-architecture. Un processeur SIMD actuel dispose d'une unité de calcul très élaborée, capable de faire plusieurs calculs en parallèle, au moins un pour chaque élément du vecteur. Par exemple, si un vecteur contient 16 entiers, l'ALU SIMD doit contenir au moins 16 additionneurs entiers. Mais sur un processeur vectoriel, ce n'est pas le cas : l'unité de calcul est en réalité une unité de calcul entière/flottante normale, mais qui a la particularité d'être pipelinée. Les calculs sont donc effectués en parallèle, mais d'une manière totalement différente.
La plupart de ces différences s'expliquent par le fait que les anciens processeurs vectoriels devaient faire avec des limitations en termes de circuits. Ils avaient peu de transistors à leur disposition, ce qui fait que leur unité de calcul devait être la plus simple possible. D'où l'utilisation d'une unité de calcul simple, mais utilisée de manière pipelinée. De plus, les processeurs vectoriels ne possèdent aucune mémoire cache pour les données et se contentent juste de caches d'instruction. De tels caches sont généralement peu utiles quand on manipule des tableaux, chose quasiment systématique quand on travaille avec du parallélisme de données.
===Une unité de calcul pipelinée===
La différence entre processeur vectoriel et SIMD tient dans la façon dont sont traités les vecteurs : les instructions SIMD traitent chaque élément en parallèle, alors que les processeurs vectoriels pipelinent ces calculs ! Par pipeliner, on veut dire que l’exécution de chaque instruction est découpée en plusieurs étapes indépendantes. Au lieu d'attendre la fin de l’exécution d'une opération avant de passer à la suivante, on peut commencer le traitement d'une nouvelle donnée sans avoir à attendre que l'ancienne soit terminée.
[[File:Pipeline.png|centre|vignette|upright=2|Pipeline vectoriel.]]
Pour donner un exemple, on peut donner l'exemple d'une multiplication flottante effectuée entre deux registres. Son exécution peut être décomposée en plusieurs étapes.
Par exemple, on peut avoir 3 étapes :
* une première étape E qui va additionner les exposants et gérer les diverses exceptions ;
* une étape M qui va multiplier les mantisses ;
* et enfin une étape A qui va arrondir le résultat.
L’exécution de notre opération flottante sur un vecteur donnerait donc quelque chose dans le genre, où chaque ligne correspond au traitement d'un nouvel élément dans un vecteur, dans un paquet SIMD.
[[File:Multiplication vectorielle - pipeline.png|centre|vignette|upright=2|Multiplication vectorielle - pipeline]]
: Certains processeurs vectoriels utilisent plusieurs ALU pipelinées pour accélérer les calculs. On peut les voir comme des intermédiaires entre processeur vectoriels et SIMD SWAR.
Avec une unité de calcul pipelinée découpée en N étages, on peut gérer N données simultanées : autant qu'il y a d'étapes différentes. Mais ce nombre maximal de données met un certain temps avant d'être atteint. L'unité de calcul met du temps avant d'arriver à son régime de croisière. Durant ce temps, elle n'a pas commencé à traiter suffisamment d’éléments pour que toutes les étapes soient occupées. Ce temps de démarrage est strictement égal du nombre d'étapes nécessaires pour effectuer une instruction. La même chose arrive vers la fin du vecteur, quand il ne reste plus suffisamment d’éléments à traiter pour remplir toutes les étapes.
[[File:Startup and dead time vector pipeline.png|centre|vignette|upright=2|Startup and dead time vector pipeline]]
Pour amortir ces temps de démarrage et de fin, certains processeurs démarrent une nouvelle instruction sans attendre la fin de la précédente : deux instructions peuvent se chevaucher pour remplir les vides. Par contre, il peut y avoir des problèmes lorsque deux instructions qui se suivent manipulent le même vecteur. Il faut que la première instruction ait fini de manipuler un élément avant que la suivante ne veuille commencer à le modifier. Pour cela, il faut avoir des vecteurs suffisamment qui contiennent plus d’éléments qu'il n'y a d'étapes pour effectuer notre instruction.
===La technique du ''chaining''===
La technique du pipeline peut encore être améliorée dans certains cas particuliers. L'idée est simplement d'ajouter la technique du contournement (''bypass'') à l'unité de calcul. Pour rappel, le contournement connecte la sortie de l'ALU à une de ses entrées, ce qui permet au résultat d'un calcul d'être réutilisable au cycle d’horloge suivant. L'usage du contournement sur les processeurs vectoriels porte le nom de '''''Vector Chaining'''''. Un processeur implémentant le ''chaining''/''bypass'' a toutes ses unités de calcul reliées entre elles : la sortie d'une unité est reliée aux entrées de toutes les autres.
Pour comprendre à quoi peut servir cette technique, on peut citer deux grands exemples principaux. Le premier avantage est que l'implémentation des instructions SIMD horizontales est beaucoup plus simple. C'est pour cela que les processeurs vectoriels intègrent souvent des instructions horizontales, et notamment des instructions arithmétiques horizontales. Le contraste avec les processeurs SIMD récents, qui utilisent plusieurs ALU travaillant en parallèle, est frappant. Ces derniers n’intègrent pas d'instructions horizontales arithmétiques, les seules opérations horizontales supportées sont généralement des permutations ou des instructions ''compress/expand'', guère plus.
Maintenant, introduisons une autre possibilité par un exemple. Dans cet exemple, on suppose que le processeur dispose d'une ALU pour l'addition et d'une autre pour la multiplication. Imaginons que l'on ait trois vecteurs nommés A, B et C. Pour chaque énième élément de ces paquets, je souhaite effectuer le calcul <math>A_n + B_n \times C_n</math>. En théorie, il faudrait faire d'abord la multiplication, stocker le résultat temporaire de la multiplication dans un registre vectoriel, puis faire l'addition. Mais en rusant un peu, on peut utiliser le pipeline plus efficacement. Une fois que le premier élément de la multiplication du premier vecteur est connu, pourquoi ne pas démarrer l'addition immédiatement après, et continuer la multiplication en parallèle ? Après tout, les deux calculs ont lieu dans des ALUs séparés. Et le contournement permet d'alimenter l'ALU d'addition avec la sortie du circuit multiplieur.
[[File:Vector chaining.png|centre|vignette|upright=2|Vector chaining]]
===La gestion des vecteurs de taille variable===
L'usage d'une unité de calcul pipelinée fait que la taille des vecteurs n'est pas forcément fixe. Autant les processeurs SIMD non-vectoriels disposent d'un nombre fixe d'unités de calcul, et peuvent donc gérer des vecteurs de taille fixe, autant ce n'est pas le cas des processeurs vectoriels. Une unité de calcul pipelinée à 16 étage peut utiliser les trois premiers pour traiter un vecteur de 3 élements, les 5 suivants pour un second vecteur de 5, et le reste pour un troisième vecteur. Reste que la gestion des vecteurs de taille variable doit être gérée au niveau du séquenceur, mais surtout : doit être permise au niveau du jeu d'instruction.
Pour cela, il faut ajouter au jeu d'instruction de quoi indiquer la taille du vecteur en cours de traitement. Et c'est le rôle d'un registre spécialisé, le '''Vector Length Register''', que de gérer les vecteurs de taille variable. Le ''Vector Length Register'' est un registre qui indique combien d’éléments on doit traiter dans un vecteur. On peut ainsi dire au processeur : je veux que tu ne traites que les 40 premiers éléments présents d'un paquet. Ils sont utilisés pour gérer des tableaux dont la taille n'est pas un multiple d'un vecteur. Quand on arrive à la fin d'un tableau, il suffit de configurer le ''Vector Length Register'' pour ne traiter que ce qu'il faut.
[[File:Vector length register.png|centre|vignette|upright=2|Vector length register.]]
Il facilite l'usage du '''déroulage de boucle''', une optimisation qui vise à réduire le nombre d'itérations d'une boucle en dupliquant son corps en plusieurs exemplaires. Le compilateur réplique le corps de la boucle (les instructions à répéter) en plusieurs exemplaires dans cette boucle, avant de corriger le nombre d'itérations de la boucle. Par exemple, prenons cette boucle, écrite dans le langage C :
<syntaxhighlight lang="c">
int i;
for (i = 0; i < 100; ++i)
{
a[i] = b[i] * 7 ;
}
</syntaxhighlight>
Celle-ci peut être déroulée comme suit :
<syntaxhighlight lang="c">
int i;
for (i = 0; i < 100; i+=4)
{
a[i] = b[i] * 7 ;
a[i+1] = b[i+1] * 7 ;
a[i+2] = b[i+2] * 7 ;
a[i+3] = b[i+3] * 7 ;
}
</syntaxhighlight>
Les instructions vectorielles permettent de traiter plusieurs éléments à la fois, ce qui fait que plusieurs tours de boucles peuvent être rassemblés en une seule instruction SIMD. Le déroulage de boucles permet d'exposer des situations où ce regroupement est possible. Dans notre exemple, si jamais notre processeur dispose d'une instruction de multiplication capable de traiter 4 éléments du tableau a ou b en une seule fois, la boucle déroulée peut être vectorisée assez simplement en utilisant une multiplication vectorielle (que nous noterons vec_mul).
<syntaxhighlight lang="c">
int i;
for (i = 0; i < 100; i+=4)
{
vec_a[i] = vec_mul ( vec_b[i] , 7 ) ;
}
</syntaxhighlight>
Le déroulage de boucles n'est toutefois pas une optimisation valable pour toutes les boucles. Reprenons l'exemple de la boucle vue plus haut. Si jamais le tableau à manipuler a un nombre d’éléments qui n'est pas multiple de 4, la boucle ne pourra être vectorisée, vu que la multiplication vectorielle ne peut traiter que 4 éléments à la fois. Pour ce faire, les compilateurs utilisent généralement deux boucles : une qui traite les éléments du tableau avec des instructions SIMD, et une autre qui traite les éléments restants avec des instructions non vectorielles. Cette transformation s'appelle le '''strip-mining'''.
Par exemple, si je veux parcourir un tableau de taille fixe contenant 102 éléments, je devrais avoir une boucle comme celle-ci :
<syntaxhighlight lang="c">
int i;
for (i = 0; i < 100; i+=4)
{
a[i] = b[i] * 7 ;
a[i+1] = b[i+1] * 7 ;
a[i+2] = b[i+2] * 7 ;
a[i+3] = b[i+3] * 7 ;
}
for (i = 100; i < 102; ++i)
{
a[i] = b[i] * 7 ;
}
</syntaxhighlight>
Les processeurs vectoriels utilisent le ''Vector Length Register'' pour éviter d'utiliser le strip-mining. Avec ce registre, il est possible de demander aux instructions vectorielles de ne traiter que les n premiers éléments d'un vecteur : il suffit de placer la valeur n dans le ''Vector Length Register''. Évidemment, n doit être inférieur au nombre d’éléments maximal du vecteur. Avec ce registre, on n'a pas besoin d'une seconde boucle pour traiter les éléments restants, et une simple instruction vectorielle peut suffire.
L'avantage principal est que cela réduit la portion de code non-parallélisable, donc augmente l'efficience SIMD. Mais l'intérêt n'est pas qu'une question de code. Il faut aussi prendre en compte que cela permet d'utiliser l'ALU plus efficacement. Dès qu'un vecteur court est terminé, le processeur peut immédiatement démarrer le calcul d'un autre vecteur, le vecteur suivant, l'instruction suivante, sans laisser de cycles inutilisés. Avec un processeur SIMD non-vectoriel, on aurait du utiliser de la prédication, donc utiliser seulement une partie des ALU SIMD, ce qui aurait été un gâchis.
===Les accès mémoire===
Les accès mémoire des processeurs vectoriels sont sensiblement les mêmes que ceux des autres processeurs SIMD. Les instructions d'accès mémoire sont les mêmes, on retrouve les modes d'adressages précédents, dont les accès en ''scatter-gather'' et en ''stride''.
Sur certains processeurs vectoriels, les instructions vectorielles lisent et écrivent en mémoire RAM, sans passer par des registres : on parle de '''processeurs vectoriels mémoire-mémoire'''. Elles n'avaient pas d'instructions LOAD et STORE, les instructions arithmétiques géraient directement la lecture des opérandes en mémoire, et incrémentaient automatiquement les adresses à lire/écrire. Elles pouvaient gérer des vecteurs de taille arbitraire, la gestion d'un tableau se faisait parfois en une seule instruction ! Le problème est que l'absence de registres pour stocker les données lues réduisait grandement les performances.
[[File:Processeur vectoriel mémoire-mémoire.png|centre|vignette|upright=2|Processeur vectoriel mémoire-mémoire]]
Les processeurs vectoriels mémoire-mémoire étaient cependant minoritaires, la grosse majorité des processeurs vectoriels récents possèdent des registres vectoriels dédiés aux vecteurs. La plupart sont des architectures de type LOAD-STORE, mais pas toute. De fait, il n'y a pas de lien strict entre adressage des opérandes et processeurs vectoriel (ou SIMT). Seuls les processeurs SIMD de type SWAR ont une restriction là-dessus et sont systématiquement des architectures LOAD-STORE.
[[File:Processeur vectoriel à registres vectoriels.png|centre|vignette|upright=2|Processeur vectoriel à registres vectoriels.]]
Il faut noter que l'implémentation des accès mémoire est potentiellement différente de celle des autres processeurs SIMD. En principe, on peut utiliser une implémentation identique entre les deux. Mais le fait que les processeurs vectoriels sont pipelinés fait qu'il est préférable d'utiliser une implémentation différente, qui se base sur une RAM ou des caches pipelinés. Vu que l'ALU reçoit une/deux opérandes par cycle, l'idéal est que la mémoire ou le cache aillent au même rythme et soient donc pipelinés.Bien sur, il est possible d'utiliser une implémentation plus classique, avec des caches multiports, un chargement en plusieurs fois des vecteurs depuis le cache, des circuits pour réorganiser les accès mémoire, etc.
En théorie, les architectures vectorielles font peu usage de mémoires cache. Les transferts entre registres vectoriels et mémoire RAM sont directs, sans intermédiaire. La raison est que les applications qui tirent parti du SIMD ont une bonne localité spatiale, mais une mauvaise localité temporelle. Or, les caches de données profitent surtout de la localité temporelle pour donner de bonnes performances. De plus, rappelons que les processeurs vectoriels sont assez anciens et datent d'une époque où les transistors étaient précieux. Il valait mieux utiliser des transistors dans un gros cache d'instruction que pour un cache de donnée peu efficace.
==Les systèmes SIMD à plusieurs processeurs : le SIMT==
Enfin, terminons avec le dernier type de SIMD, qui date du tout début de l'informatique : celui où on combine plusieurs processeurs non-SIMD. L'idée est de synchroniser plusieurs processeurs de manière à ce qu'ils exécutent la même instruction en même temps, chacun sur des données différentes. On parle d''''exécution ''lockstep''''' des instructions. Toute la difficulté est de garantir la synchronisation des processeurs, garantir que tous les processeurs exécutent la même instruction. Ce qui pose problème quand des branchements sont impliqués, des processeurs pouvant prendre le branchement et pas les autres.
===Les ''array processors'' des temps anciens===
Les premières architectures matérielles conçues pour le parallélisme de données étaient de ce type. On peut par exemple citer l'exemple des Thinking machines CM-1 et CM-2, qui exécutaient tous la même instruction au même cycle d'horloge sur 64000 processeurs minimalistes. Un autre exemple est celui de l'ILLIAC IV. Historiquement, le terme utilisé pour désigner de telles architectures était '''array processors''', encore que ce terme fût réservé aux systèmes multiprocesseurs. Par la suite, le terme ''Single Instruction Multiple Threads'', ou SIMT, a été utilisé. Mais ce terme a ensuite été repris par le marketing de NVIDIA pour désigner une technique précise de SIMD avec prédication, utilisée sur ses cartes graphiques récentes. Le mauvais usage de la terminologie fait que le terme SIMT est devenu polysémique et quelque peu trompeur.
L'ILLIAC 4 était un ordinateur un peu particulier, presque unique en son genre. Son architecture était composé d'un processeur principal couplé à plusieurs processeurs secondaires. Il y avait en tout un processeur principal et 64 processeurs secondaires.
Le processeur principal chargeait les instructions depuis la mémoire, les décodaient, puis envoyait les instructions aux processeurs secondaires. Le processeur principal était le seul à avoir un ''program counter'', il était utilisé pour gérer les boucles, ainsi que pour configurer les instructions à prédication. Cependant, le processeur principal n'était pas un simple séquenceur : il pouvait exécuter des instructions, avait un accumulateur et des registres, etc. Il prenait en charge toute instruction non-SIMD, alors que les instructions SIMD étaient envoyées aux processeurs secondaires.
Les 64 processeurs secondaires avaient chacun : une unité de calcul, un ''local store'' de 2048 flottants de 64 bits, une unité mémoire. Ils n'ont pas de séquenceur ni de ''program counter'', ni de quoi charger une instruction. L'unité de calcul est appelée un ''processing element'' ou PE. Elle intègre un additionneur flottant, de quoi faire des décalages, et une unité de calcul logique. De plus, un PE intègre des registres accumulateurs et de quoi calculer des adresses : un registre d'indice, un registre d'adresse, un circuit de calcul d'adresse. L'unité mémoire connectait l'ALU entière PE et le ''local store'', et elle permettait aussi d'adresser des entrées-sorties. Pour gérer la prédication, les processeurs secondaires pouvaient être activés ou désactivés suivant le résultat des branchements/conditions.
Intuitivement, on peut faire le rapprochement avec un processeur SIMD. Le processeur principal regrouperait la partie non-SIMD d'un processeur SIMD, alors que les processeurs secondaires seraient l'unité de calcul SIMD. Cependant, il y a une différence très importante : il n'y a pas de banc de registre vectoriel. À la place, chaque processeur secondaire a son propre banc de registre, et ce sont des registres scalaires ! Une autre différence est que la mémoire RAM est découpée en plusieurs ''local store'' de 2048 adresses, chacun étant relié à une unité de calcul PE. N'oublions pas le système de calcul d'adresse et l'unité LOAD/STORE qui est fusionnée avec l'unité SIMD, chaque PE ayant ses circuits de calcul d'adresse, sa propre unité mémoire, etc.
===Les anciennes cartes graphiques hybrides SIMD-VLIW===
Une technique de SIMD multi-processeur a été utilisée autrefois sur certaines cartes graphiques AMD à architecture Terascale. La différence est que l'architecture en question était multicœur et non multi-processeurs. Elles contenaient plusieurs cœurs VLIW, qui fonctionnaient en ''lockstep''. Un groupe de 16 processeurs VLIWs exécutaient la même opération VLIW. Du moins, c'est ce que semblent dire les [https://www.techpowerup.com/gpu-specs/docs/amd-gcn1-architecture.pdf white papers de présentation de l'architecture GCN]. L'usage de cet hybride SIMD-VLIW était très adapté au traitement graphique.
Les cartes graphiques post-années 2000 sont capables d'exécuter des programmes appelés des '''''shaders''''', qui sont utilisés en rendu 3D, pour calculer certaines scènes 3D, et notamment pour calculer les ombres (d'où leur nom de shader, pour shade - ombre). Or, ces shaders sont justement exécutés par la carte graphique, sur les processeurs de shaders. Un processeur de ''shader'' applique un programme/''shader'' sur chaque ''pixel'' ou triangle d'une scène 3D (en fait des fragments et des vertices, mais faisons cette simplification). Chaque triangle ou pixel d'une scène 3D peut être traité indépendamment des autres, ce qui rend le traitement 3D fortement parallèle. Aussi, l'usage du SIMD est assez naturel : chaque processeur exécute un ''shader'' sur un pixel/triangle, utiliser une architecture SIMD parait naturel. Reste à justifier l'usage de plusieurs CPU VLIW.
La raison tient dans la manière dont est traité un pixel par un shader. Un pixel est codé en utilisant quatre informations. Premièrement, une couleur codée avec trois nombres flottants, qui représentent une couleur au format RGB avec un mélange de rouge, vert et bleu, chaque couleur rouge/bleu/vert étant codée avec un flottant de 32 bits. La quatrième information est un nombre qui code la transparence du pixel. Généralement, le shader traite différemment la transparence de la couleur RGB, dans le sens où les instructions utilisées ne sont pas les mêmes. Au lieu de traiter couleur RGB et transparence séparément, elles sont traitées en même temps en parallèle, ce qui demande de pouvoir émettre deux instructions : une pour le calcul de la transparence, une autre pour la couleur RGB. Fait amusant, l'instruction utilisée pour gérer la composante RGB est une instruction vectorielle qui travaille sur des vecteurs de 3 couleurs. Les architectures VLIW sont parfaites pour cela.
Les cartes graphiques de l'époque de DirectX 9 utilisaient cette méthode qui mélangeait SIMD et VLIW. Elles disposaient de plusieurs cœurs VLIW qui exécutaient le même shader en exécution ''lockstep''. Chaque cœur traitait au moins un pixel à la fois, ou un triangle à la fois. Cependant, nous devons préciser que les cœurs VLIW partagent la même unité de contrôle, ce sont en réalité des chemins de données plus qu'autre chose. L'architecture était aussi combinée avec du ''Fine Grained Multithreading'' afin de pouvoir gérer les accès mémoire, de telles architectures ne pouvant pas se permettre d'utiliser l'exécution dans le désordre. De telles architectures multicœurs à exécution ''lockstep'' permettent quelques optimisations, comme le fait de partager le cache d'instruction entre les cœurs.
==Résumé des différents types de processeurs SIMD==
Dans ce chapitre, nous avons appris qu'il existe plusieurs catégories de processeurs SIMD. Les différences entre ces catégories tiennent à la fois dans le jeu d'instruction, mais aussi dans la microarchitecture des processeurs en question.
Au niveau du jeu d'instruction, il y a trois paliers, qui sont résumés dans le tableau ci-dessous.
{|class="wikitable"
|-
!
! Support de la prédication
! Taille des vecteurs
! Instructions horizontales
! Architecture LOAD-STORE
! Pointeur de pile
|-
! Processeurs SIMD purs
| Non
| rowspan="3" | Fixe
| rowspan="3" | Limitées à des échanges de données à l'intérieur d'un vecteur, pas d'instructions de réduction
| Oui
| rowspan="2" | Unique
|-
! Processeurs SIMD avec prédication
| Oui, par définition
| rowspan="3" | Dépend du processeur
|-
! Processeurs SIMT
| rowspan="2" | Oui, sauf exceptions
| Un pointeur de pile possible par élément du vecteur
|-
! Processeurs vectoriels
| Variable
| Toutes supportées, y compris les instructions arithmétiques horizontales
| Unique
|}
Au niveau de la microarchitecture, il y a trois paliers, qui sont résumés dans le tableau ci-dessous.
{|class="wikitable"
|-
!
! Unité de calcul
! Banc de registre
! Adressage des opérandes
|-
! Processeurs SIMD
| rowspan="2" | Plusieurs ALU simples non-pipelinée, plusieurs additionneurs/multiplieurs/autres
| Banc de registres vectoriels
| Architecture LOAD-STORE
|-
! Processeurs SIMT
| Plusieurs bancs de registres scalaires (non-vectoriels, registres normaux)
| rowspan="2" | Dépend du processeur
|-
! Processeurs vectoriels
| ALU simple unique, pipelinée
| Banc de registres vectoriels
|}
Les processeurs SIMD purs ont des performances différentes des processeurs vectoriels, vu que les premiers font leurs calculs en parallèle, alors que les CPU vectoriels utilisent un pipeline. Mais dans les grandes lignes, les performances sont similaires. Les différences sont grandement atténuées par l'usage du ''vector chaining'', de la gestion des vecteurs de taille variables, et des autres optimisations spécifiques aux processeurs vectoriels. L'avantage est cependant du côté des processeurs SIMD pur.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Architectures multithreadées et Hyperthreading
| prevText=Architectures multithreadées et Hyperthreading
| next=La cohérence des caches
| nextText=La cohérence des caches
}}
{{autocat}}
</noinclude>
shqtazmj3oww18yy31sf979d8adw1c4
Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs
0
65962
773398
764907
2026-09-27T23:38:06Z
Mewtow
31375
/* Annexe : les architectures à cœurs conjoints */
773398
wikitext
text/x-wiki
Pour réellement tirer parti du parallélisme de taches, rien ne vaut l'utilisation de plusieurs processeurs et/ou de plusieurs cœurs, qui exécutent chacun un ou plusieurs programmes dans leur coin. Des solutions multiprocesseurs ont alors vu le jour pour rendre l'usage de plusieurs processeurs plus adéquat. Avant de poursuivre, nous allons voir les systèmes multiprocesseur à part des processeurs multicœurs. Il faut dire qu'utiliser plusieurs processeurs et avoir plusieurs cœurs sur la même puce, ce n'est pas la même chose. Particulièrement pour ce qui est de la mémoire cache.
Les '''systèmes multiprocesseur''' placent plusieurs processeurs sur la même carte mère. Ils sont courants dans les serveurs ou les ''data centers'', mais sont beaucoup plus rares pour les ordinateurs grand public. Il y a eu quelques systèmes multiprocesseur vendus au grand public dans les années 2000, certaines cartes mères avaient deux sockets pour mettre deux processeurs. Mais les logiciels et les systèmes d'exploitation grand public n'étaient pas adaptés pour, ce qui fait que la technologie est restée confidentielle.
Puis, en 2005, les '''processeurs multicœurs''' sont arrivés. Ils peuvent être vus comme un regroupement de plusieurs processeurs dans le même circuit intégré. Pour être plus précis, ils contiennent plusieurs ''cœurs'', chaque cœur pouvant exécuter un programme tout seul. Un cœur dispose de toute la machinerie électronique pour exécuter un programme, que ce soit un séquenceur d'instruction, des registres, une unité de calcul. Par contre, certains circuits du processeur sont partagés entre les cœurs, comme les circuits d’interfaçage avec la mémoire.
Les processeurs multicœurs sont devenus la norme dans les ordinateurs grand public et les logiciels et systèmes d'exploitation se sont adaptés. Suivant le nombre de cœurs présents dans notre processeur, celui-ci sera appelé un processeur double-cœur (deux cœurs), quadruple-cœur (4 cœurs), octuple-cœur (8 cœurs), etc. Les processeurs grand public ont généralement entre 8 et 16 cœurs, à l'heure où j'écris ces lignes (2025), rarement au-delà. Par contre, les processeurs pour serveurs dépassent la vingtaine de cœurs. Les serveurs utilisent souvent des architectures dites '''''many core''''', qui ont un très grand nombre de cœurs, plus d'une cinquantaine, voire plusieurs centaines ou milliers.
: Dans ce qui suit, nous utiliserons les termes "processeurs" et "cœurs" comme s'ils étaient synonymes. Tout ce qui vaut pour les systèmes multiprocesseur vaut aussi pour les systèmes multicœurs.
==Le partage des caches==
Quand on conçoit un processeur multicœur, il ne faut pas oublier ce qui arrive à la pièce maîtresse de tout processeur actuel : le cache ! Pour le moment nous allons oublier le fait que les processeurs ont une hiérarchie de caches, avec des caches L1, L2, L3 et autres. Nous allons partir du principe qu'un processeur simple cœur a un seul cache, et voir comment adapter le cache à la présence de plusieurs cœurs. Nous allons rapidement lever cette hypothèse, pour étudier le cas où un processeur multicœur a une hiérarchie de caches, mais seulement après avoir vu le cas le plus simple à un seul cache.
===Le partage des caches sans hiérarchie de caches : caches dédiés et partagés===
Avec un seul niveau de cache, sans hiérarchie, deux solutions sont possibles. La première consiste à garder un seul cache, et de le partager entre les cœurs. L'autre solution est de dupliquer le cache et d'utiliser un cache par cœur. Les deux solutions sont appelées différemment. On parle de '''caches dédiés''' si chaque cœur possède son propre cache, et de '''cache partagé''' avec un cache partagé entre tous les cœurs. Ces deux méthodes ont des inconvénients et des avantages.
{|
|[[File:Caches dédiés.png|vignette|Caches dédiés]]
|[[File:Caches partagés.png|vignette|Cache partagé]]
|}
Le premier point sur lequel comparer caches dédiés et partagés est celui de la capacité du cache. La quantité de mémoire cache que l'on peut placer dans un processeur est limitée, car le cache prend beaucoup de place, près de la moitié des circuits du processeur. Aussi, un processeur incorpore une certaine quantité de mémoire cache, qu'il faut répartir entre un ou plusieurs caches. Les caches dédiés et partagés ne donnent pas le même résultat. D'un côté, le cache partagé fait que toute la mémoire cache est dédiée au cache partagé, qui est très gros. De l'autre, on doit répartir la capacité du cache entre plusieurs caches séparés, individuellement plus petits. En conséquence, on a le choix entre un petit cache pour chaque processeur ou un gros cache partagé.
Le choix entre les deux n'est pas simple, mais doit tenir compte du fait que les programmes exécutés sur les cœurs n'ont pas les mêmes besoins. Certains programmes sont plus gourmands et demandent beaucoup de cache, alors que d'autres utilisent peu la mémoire cache. Avec un cache dédié, tous les programmes ont accès à la même quantité de cache, car les caches des différents cœurs sont de la même taille. Les caches dédiés étant assez petits, les programmes plus gourmands devront se débrouiller avec un petit cache, alors que les autres programmes auront du cache en trop.
À l'opposé, un cache partagé répartit le cache de manière optimale : un programme gourmand peut utiliser autant de cache qu'il veut, laissant juste ce qu'il faut aux programmes moins gourmands. le cache peut être répartit plus facilement selon les besoins des différents programmes.
[[File:Cache partagé contre cache dédié.png|centre|vignette|upright=2.5|Cache partagé contre cache dédié]]
Un autre avantage des caches partagés est quand plusieurs cœurs accèdent aux même données. C'est un cas très courant, souvent lié à l'usage de mémoire partagé ou de ''threads''. Avec des caches dédiés, chaque cœur a une copie des données partagées. Mais avec un cache partagé, il n'y a qu'une seule copie de chaque donnée, ce qui utilise moins de mémoire cache. Imaginons que l'on sait 8 caches dédiés de 8 Kibioctets, soit 64 kibioctets au total, comparé à un cache partagé de même capacité totale. Les doublons dans les caches dédiés réduiront la capacité mémoire utile, effective, comparé à un cache partagé. S'il y a 1 Kibioctet de mémoire partagé, 8 kibioctets seront utilisés pour stocker ces données en doublons, seulement 1 kibioctet sur un cache partagé. Ajoutons aussi que la cohérence des caches est grandement simplifiée avec l'usage d'un cache partagé, vu que les données ne sont pas dupliquées dans plusieurs caches.
Mais le partage du cache peut se transformer en inconvénient si les programmes entrent en compétition pour le cache, que ce soit pour y placer des données ou pour les accès mémoire. Deux programmes peuvent vouloir accéder au cache en même temps, voire carrément se marcher sur les pieds. La résolution des conflits d'accès au cache est résolu soit en prenant un cache multiport, avec un port dédié par cœur, soit par des mécanismes d'arbitrages avec des circuits dédiés. Le revers de la médaille tient au temps de latence. Plus un cache est gros, plus il est lent. En conséquence, des caches dédiés seront plus rapides qu'un gros cache partagé plus lent.
===Le partage des caches adapté à une hiérarchie de caches===
Dans la réalité, un processeur multicœur ne contient pas qu'un seul cache, mais une hiérarchie de caches avec des caches L1, L2 et L3, parfois L4. Dans cette hiérarchie, certains caches sont partagés entre plusieurs cœurs, les autres sont dédiés. Le cache L1 n'est jamais partagé, car il doit avoir un temps d'accès très faible. Pour les autres caches, tout dépend du processeur.
[[File:Dual Core Generic.svg|vignette|Cache L2 partagé.]]
Les premiers processeurs multicœurs commerciaux utilisaient deux niveaux de cache : des caches L1 dédiés et un cache L2 partagé. Le cache L2 partagé était relié aux caches L1, grâce à un système assez complexe d'interconnexions. Le cache de niveau L2 était souvent simple port, car les caches L1 se chargent de filtrer les accès aux caches de niveau inférieurs.
Les processeurs multicœurs modernes ont des caches L3 et même L4, de grande capacité, ce qui a modifié le partage des caches. Le cache de dernier niveau, à savoir le cache le plus proche de la mémoire, est systématiquement partagé, car son rôle est d'être un cache lent mais gros. Il s'agit le plus souvent d'un cache de L3, plus rarement L4. Sur certains processeurs multicœurs, le cache de dernier niveau n'est techniquement pas dans le cœur, mais fait partie d'un ensemble de circuits reliés, comme le contrôleur mémoire ou l'interface mémoire. Il fonctionne à une fréquence différente du processeur, n'a pas la même tension d'alimentation, etc.
Le cas du cache L2 dépend des architectures : il est partagé sur certains processeurs, dédié sur d'autres. Mais sur les processeurs modernes, c'est un cache dédié soit par cœur, soit pour un groupe de cœurs. Dans le cas le plus courant, chaque cache L2 est partagé entre plusieurs cœurs mais pas à tous. En effet, on peut limiter le partage du cache à quelques cœurs particuliers pour des raisons de performances.
[[File:Partage des caches sur un processeur multicoeurs.png|centre|vignette|upright=2.0|Partage des caches sur un processeur multicœur.]]
D'autres processeurs ont des caches L2 dédiés. Il s'agit surtout des processeurs multicœurs anciens, parmi les premières générations de processeurs multicœurs. Un exemple est celui de la microarchitecture Nehalem d'Intel. Il avait des caches L1 et L2 dédiés, mais un cache L3 partagé.
[[File:Nehalem EP.png|centre|vignette|upright=2.0|Partage des caches sur un processeur Intel d'architecture Nehalem.]]
===Les caches partagés centralisés et distribués===
Un point important est que quand on parle de cache partagé ou de cache dédié, on ne parle que de la manière dont les cœurs peuvent accéder au cache, pas de la manière dont le caches est réellement localisé sur la puce. En théorie, qui dit plusieurs caches dédiés signifie que l'on a vraiment plusieurs caches séparés sur la puce. Et chaque cache dédié est proche du cœur qui lui est attribué. Et pour les caches partagés unique, une portion de la puce de silicium contient le cache, que cette portion est un énorme bloc de transistors. Il est généralement placé au milieu de la puce ou sur un côté, histoire de facilement le connecter à tous les cœurs.
Mais pour les caches séparés, ce n'est pas toujours le cas. Avoir un cache énorme poserait des problèmes sur les architectures avec beaucoup de cœurs. En réalité, le cache est souvent découpé en plusieurs banques, reliées à un contrôleur du cache par un système d'interconnexion assez complexe. Les banques sont physiquement séparées, et il arrive qu'elles soient placées proche d'un cœur chacune. L'organisation des banques ressemble beaucoup à l'organisation des caches dédiés, avec une banque étant l'équivalent d'un cache dédié. La différence est que les cœurs peuvent lire et écrire dans toutes les banques, grâce au système d'interconnexion et au contrôleur de cache.
Tel était le cas sur les processeurs AMD Jaguar. Ils avaient un cache L2 de 2 mébioctets, partagés entre tous les cœurs, qui était composé de 4 banques de 512 Kibioctets. Les quatre banques du cache étaient reliées aux 4 cœurs par un réseaux d'interconnexion assez complexe.
[[File:AMDJaguarModule.png|centre|vignette|upright=2|AMD Jaguar Module]]
La différence entre les deux solutions pour les caches partagés porte le nom de cache centralisés versus distribués. Un gros cache unique sur la puce est un '''cache centralisé''', et c'est généralement un cache partagé. Mais un cache composé de plusieurs banques dispersées sur la puce est un '''cache distribué''', qui peut être aussi bien dédié que partagé.
===Les caches virtualisés===
Il faut noter que quelques processeurs utilisent cette technique pour fusionnent le cache L2 et le cache L3. Par exemple, les processeurs IBM Telum utilisent des caches L3 virtualisés, dans leurs versions récentes. Le processeur Telum 2 contient 10 caches L2 de 36 mébioctets chacun, soit 360 mébioctets de cache. L'idée est que ces 360 mébioctets sont partagés à la demande entre cache L2 dédié et cache L3. On parle alors de '''cache virtualisé'''.
Un cache de 36 mébioctet est associé à un cœur, auquel il est directement relié. Les cœurs n'utilisent pas tous leur cache dédié à 100% Il arrive que des cœurs aient des caches partiellement vides, alors que d'autres on un cache qui déborde. L'idée est que si un cœur a un cache plein, les données évincées du cache L2 privé sont déplacées dans le cache L2 d'un autre cœur, qui lui est partiellement vide. Le cache L2 en question est alors partitionné en deux : une portion pour les données associée à son cœur, une portion pour les données des L2 des autres cœurs.
Pour que la technique fonctionne, le processeur mesure le remplissage de chaque cache L2. De plus, il faut gérer la politique de remplacement des lignes de cache. Une ligne de cache évincée du cache doit être déplacé dans un autre L2, pas dans les niveaux de cache inférieur, ni dans la mémoire. Du moins, tant qu'il reste de la place dans le cache L3. De plus, une lecture/écriture dans le cache L3 demande de localiser le cache L2 contenant la donnée. Pour cela, les caches L2 sont tous consultés lors d'un accès au L3, c'est la solution la plus simple, elle marche très bien si le taux de défaut du cache L2 est faible.
Une telle optimisation ressemble beaucoup à un cache L2/L3 distribué, mais il y a quelques différences qui sont décrites dans le paragraphe précédent. Avec un L2 distribué, tout accès au L2 déclencherait une consultation de toutes les banques du L2. Avec un cache L3 virtualisé, ce n'est pas le cas. Le cache L2 associé au cœur est consulté, et c'est seulement en cas de défaut de cache que les autres caches L2 sont consultés. De plus, avec un cache L2 distribué, il n'y a pas de déplacement d'une ligne de cache entre deux banques, entre deux caches L2 physiques. Alors qu'avec un cache L3 virtualisé, c'est le cas en cas de remplacement d'une ligne de cache dans le cache L2.
Sur le processeur Telum 1, le partage du cache L2 est assez simple. Un cache L2 fait 32 mébioctets et est découpé en deux banques de 16 mébioctets. En temps normal, les premiers 16 mébioctets sont toujours associé au cache L2, au cœur associé. Les 16 mébioctets restants peuvent soit être attribués au cache L3, soit fusionnés avec les 16 premiers mébioctets. Dans le cas où le cœur associé est en veille, n'est absolument pas utilisé, les 32 mébioctets sont attribués au cache L3. Un partage assez simple, donc. Le partage du cache L2/L3 sur les processeurs Telum 2 n'est pas connu, il est supposé être plus flexible.
==Le réseau d'interconnexion entre cœurs==
Les systèmes avec plusieurs processeurs incorporent un réseau d'interconnexion pour connecter les processeurs entre eux, ainsi qu'à la mémoire RAM. Il s'agit d'un '''réseau d'interconnexion inter-processeur''', placé sur la carte mère. Les CPU multicœurs ont aussi un tel réseau d'interconnexion, pour relier les cœurs entre eux. La différence est que le réseau d'interconnexion est placé dans le processeur, pas sur la carte mère.
Les systèmes multi-cœurs modernes utilisent des réseaux d'interconnexion standardisés, les standards les plus communs étant l'HyperTransport, l'Intel QuickPath Interconnect, l'IBM Elastic Interface, le Intel Ultra Path Interconnect, l'Infinity Fabric, etc. Ils sont aussi utilisés pour faire communiquer entre eux plusieurs processeurs.
[[File:Architecture multicoeurs et réseau sur puce.png|centre|vignette|upright=1.5|Architecture multicoeurs et réseau sur puce]]
===Le bus partagé entre plusieurs cœurs===
Pour un faible nombre de coeurs/processeurs, la solution utilisée relie les processeurs entre eux grâce au bus mémoire. Le bus mémoire est donc un '''bus partagé''', avec tout ce que cela implique.
[[File:Architecture multicoeurs à bus partagé.png|centre|vignette|upright=2|Architecture multiprocesseurs à bus partagé]]
Pour les systèmes multicœurs, l'usage d'un bus partagé doit être adaptée pour tenir compte des caches partagés. Voyons d'abord le cas d'un CPU avec deux niveaux de cache, dont un cache L2 est partagé entre tous les cœurs. Les caches L1 sont reliés au cache L2 partagé par un bus, qui n'a souvent pas de nom. Nous désignerons le bus entre le cache L1 et le cache L2 : '''bus partagé''', sous-entendu partagé entre tous les caches. C'est lui qui sert à connecter les cœurs entre eux.
[[File:Architecture multicoeurs à bus partagé entre caches L1 et L2.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé entre caches L1 et L2]]
Un processeur multicœur typique a une architecture avec trois niveaux de cache (L1, L2 et L3), avec un niveau L1 dédié par cœur, un niveau L2 partiellement partagé et un L3 totalement partagé. Le bus partagé est alors difficile à décrire, mais il correspond à l'ensemble des bus qui connectent les caches L1 aux caches L2, et les caches L2 au cache L3. Il s'agit alors d'un ensemble de bus, plus que d'un bus partagé unique.
L'usage d'un bus partagé a cependant de nombreux défauts. Par exemple, les processeurs doivent se répartir l'accès au bus mémoire, il faut gérer le cas où deux processeurs accèdent au bus en même temps, etc. Pour cela, un composant dédié s'occupe de l'arbitrage entre processeurs. Il est généralement placé sur la carte mère de l'ordinateur, dans le ''chipset'', dans le pont nord, ou un endroit proche. Les processeurs doivent être conçus pour communiquer avec ce circuit d'arbitrage, ou du moins avoir un support minimal pour le multiprocesseur.
[[File:Intel486 System Arbitration.png|centre|vignette|upright=2|Système avec deux Intel486 - Arbitrage du bus mémoire.]]
D'autres défauts très importants seront abordés en détail dans le chapitre sur la cohérence des caches
===Le réseau d'interconnexion entre plusieurs cœurs===
Relier plusieurs cœurs avec des bus pose de nombreux problèmes techniques qui sont d'autant plus problématiques que le nombre de cœurs augmente. Le câblage est notamment très complexe, les contraintes électriques pour la transmission des signaux sont beaucoup plus fortes, les problèmes d'arbitrages se font plus fréquents, etc. Pour régler ces problèmes, les processeurs multicoeurs n'utilisent pas de bus partagé, mais un réseau d'interconnexion plus complexe.
Un exemple est le réseau du processeur Celle, utilisé sur la PS3. Les transferts sur ce bus se font par paquets de 128 bits. Le réseau était composé de 12 intermédiaires appelés ''Ramps''. Chaque intermédiaire communiquait avec deux autres ''ramps'' ce qui formait un anneau de ''ramps''. Les paquets de 128 bits circulaient sur cet anneau en passant d'un ''ramp'' au suivant, soit dans le sens horaires, soit dans le sens anti-horaire.
Avant d'entrer dans l'anneau, les processeurs envoient une requête à un circuit d'arbitrage, qui commande les transferts de données sur l'anneau. Si celui-ci accepte la requête, la donnée est envoyée au ''ramp'' et circule dans l'anneau. Il faut noter que le contrôleur mémoire a la priorité sur tout le reste. Le sens de transfert, horaire ou anti-horaire, est choisit de manière à prendre le chemin le plus court. Les paquets passent alors de ''ramp'' en ''ramp''. Chaque ''ramp'' reçoit le paquet, regarde l'adresse de destination du paquet, et décide de la suite. Soit le paquet lui est destiné, il le traite. Sinon, le il transmet au ''ramp'' suivant.
Le bus permet de transférer 4 paquets de 128 bits par cycle. Pour cela, les ''ramps'' avaient quatre bus, chacun de 128 bits : deux bus pour le sens horaire, et deux autres bus pour le sens anti-horaire. Il peut y avoir 3 transferts simultanés, tant que les paquets ne se recouvrent pas, mais les transferts sont de taille arbitraire. Le réseau d'interconnexion avait une fréquence de 1,6 GHz. Si vous faites les calculs, vous tomberez cependant sur un débit binaire théorique d'environ 300 gibioctets par secondes, qui n'est cependant jamais atteint en pratique, pour des raisons techniques liées à la cohérence des caches. La limite théorique est en pratique plus proche des 200 gibioctets par secondes, en tenant compte de ces limitations théoriques.
[[File:Réseau d'interconnexion du processeur CELL.png|centre|vignette|upright=2|Réseau d'interconnexion du processeur CELL]]
Un autre xemple de réseau d'interconnexion est celui des architectures AMD EPYC, de microarchitecture Zen 1. Elles utilisaient des chiplets, à savoir que le processeur était composé de plusieurs puces interconnectées entre elles. Chaque puce contenait un processeur multicoeurs intégrant un cache L3, avec un réseau d'interconnexion interne au processeur sans doute basé sur un ensemble de bus. De plus, les puces étaient reliées à une puce d'interconnexion qui servait à la fois d'interface entre les processeurs, mais aussi d'interface avec la R1AM, le bus PCI-Express, etc. La puce d'interconnexion était gravée en 14 nm contre 7nm pour les chiplets des cœurs.
{|
|[[File:AMD Epyc 7702 delidded.jpg|centre|vignette|upright=2|AMD Epyc 7702.]]
|[[File:AMD Epyc Rome Aufbau.png|centre|vignette|upright=2|Schéma fonctionnel de l'AMD Epyc.]]
|}
Le réseau d'interconnexion peut être très complexe, avec des connexions réseau, des commutateurs, et des protocoles d'échanges entre processeurs assez complexes basés sur du passage de messages. De telles puces utilisent un '''réseau sur puce''' (''network on chip''). Mais d'autres simplifient le réseau d'interconnexion, qui se résume à un réseau ''crossbar'', voire à des mémoires FIFO pour faire l'interface entre les cœurs.
Le problème principal des réseaux sur puce est que les mémoires FIFOs sont difficiles à implémenter sur une puce de silicium. Elles prennent beaucoup de place, utilisent beaucoup de portes logiques, consomment beaucoup d'énergie, sont difficiles à concevoir pour diverses raisons (les accès concurrents/simultanés sont fréquents et font mauvais ménage avec les ''timings'' serrés de quelques cycles d'horloges requis). Il est donc impossible de placer beaucoup de mémoires FIFO dans un processeur, ce qui fait que les commutateur sont réduits à leur strict minimum : un réseau d'interconnexion, un système d'arbitrage simple parfois sans aucune FIFO, guère plus.
===Les architectures en ''tile''===
Un cas particulier de réseau sur puce est celui des '''architectures en ''tile''''', des architectures avec un grand nombre de cœurs, connectés les unes aux autres par un réseau d'interconnexion "rectangulaire". Chaque cœur est associé à un commutateur (''switch'') qui le connecte au réseau d'interconnexion, l'ensemble formant une ''tile''.
[[File:Tile64-Tile.svg|centre|vignette|upright=1.5|''Tile'' de base du Tile64.]]
Le réseau est souvent organisé en tableau, chaque ''tile'' étant connectée à plusieurs voisines. Dans le cas le plus fréquent, chaque ''tile'' est connectée à quatre voisines : celle du dessus, celle du dessous, celle de gauche et celle de droite. Précisons que cette architecture n'est pas une architecture distribuée dont tous les processeurs seraient placés sur la même puce de silicium. En effet, la comparaison ne marche pas pour ce qui est de la mémoire : tous les cœurs accèdent à une mémoire partagée située en dehors de la puce de silicium. Le réseau ne connecte pas plusieurs ordinateurs séparés avec chacun leur propre mémoire, mais plusieurs cœurs qui accèdent à une mémoire partagée.
Un bon exemple d'architecture en ''tile'' serait les déclinaisons de l'architecture Tilera. Les schémas du-dessous montrent l'architecture du processeur Tile 64. Outre les ''tiles'', qui sont les éléments de calcul de l'architecture, on trouve plusieurs contrôleurs mémoire DDR, divers interfaces réseau, des interfaces série et parallèles, et d'autres entrées-sorties.
[[File:Tile64.svg|centre|vignette|upright=2|Architecture Tile64 du Tilera.]]
==Les interruptions inter-processeurs==
Les '''interruptions inter-processeurs''' sont des interruptions déclenchées sur un processeur et exécutées sur un autre. Elles sont très utiles pour le système d'exploitation, pour des raisons qu'on ne peut pas expliquer ici. Disons simplement qu'elles permettent de répartir des programmes/''threads'' sur plusieurs processeurs. Un programme/''thread'' est démarré par une interruption, et le système d'exploitation détermine sur quel processeur elle doit être exécutée. L'utilité des interruptions inter-processeur est assez variée. Autrefois, elles servaient aussi pour la cohérence des caches, mais nous détaillerons cela dans un futur chapitre.
Une interruption inter-processeurs peut être envoyée soit à un cœur bien précis, soit à n'importe quel cœur, soit à tous les cœurs, voire même revenir à l'envoyeur. Tout dépend de ce que décide le système d'exploitation. Les trois situations ne sont pas identiques, sur un point : comment préciser quel est le processeur de destination ? Si on envoie une interruption à un cœur bien précis, il faut préciser quel est le cœur qui réceptionne l'interruption. Pour cela, il n'y a pas 36 solutions : on numérote les processeurs/cœurs avec un '''numéro de processeur'''. Ce numéro leur est soit attribué au démarrage par le BIOS, soit est gravé dans leur silicium pour les processeurs multicœurs.
Pour le reste, les interruptions inter-processeurs sont identiques aux interruptions normales. Elles ont un système de priorités, certaines devant passer avant les autres, là encore défini par des ''Interrupt Request Levels'' (IRQLs) ou quelque chose de similaire. Il peut y avoir des interruptions inter-processeur de type logicielles, à savoir lancées par une instruction machine. Par exemple, sur les IBM System/360 et les ''mainframes'' z/Architecture, le processeur avait une instruction SIGNAL PROCESSOR pour déclencher des interruptions logicielles inter-processeur.
Pour générer des interruptions inter-processeur, le contrôleur d'interruption doit pouvoir rediriger des interruptions déclenchées par un processeur vers un autre. Pour expliquer comment, nous allons étudier le cas des CPU x86, mais les implémentations ARM ou autres sont très similaires. L'ancien contrôleur d'interruption 8259A ne gérait pas les interruptions inter-processeurs, ce qui fait que les cartes mères multiprocesseurs devaient incorporer un contrôleur d'interruption spécial en complément. Par contre, son successeur, l'APIC, les gérait nativement.
De nos jours, chaque cœur x86 possède son propre contrôleur d’interruption, le '''''local APIC''''', qui gère les interruptions en provenance ou arrivant vers ce processeur. On trouve aussi un '''''IO-APIC''''', qui gère les interruptions en provenance des périphériques et de les redistribuer vers les APIC locaux. L'IO-APIC gère aussi les interruptions inter-processeurs en faisant passer les interruptions d'un local APIC vers un autre. Tous les APIC locaux et l'IO-APIC sont reliés ensembles par un '''bus APIC''' spécialisé, par lequel ils vont pouvoir communiquer et s'échanger des demandes d'interruptions.
[[File:Contrôleurs d'interrptions sur systèmes x86 multicoeurs.png|centre|vignette|upright=1.5|Contrôleurs d’interruptions sur systèmes x86 multicœurs.]]
Le déclenchement d'une interruption inter-processeur se fait en écrivant dans un registre appelé l''''''Interrupt Command Register'''''. Un détail important est que l'écriture se fait dans le ''local APIC'' de l'envoyer, du processeur qui veut envoyer une interruption, pas dans le registre du processeur qui doit réceptionner l'interruption ! Le registre est composé de deux registres de 32 bits, et mémorise : le numéro du processeur de destination, le mode de transfert (à tous, à un cœur, etc), le numéro du vecteur d'interruption (pour préciser quelle interruption exécuter), et quelques informations supplémentaires. À charge de l'IO-APIC de faire ce qu'il faut en fonction du contenu de ce registre.
==Le multiprocesseur/multicœur asymétrique==
Sur les processeurs grand public actuels, les cœurs d'un processeur multicœurs sont tous identiques. Mais ce n'est certainement pas une obligation. On peut très bien regrouper plusieurs cœurs très différents, par exemple un cœur principal avec des cœurs plus spécialisés autour. Il faut ainsi distinguer le '''multicœurs symétrique''', dans lequel on place des processeurs identiques sur la même puce de silicium, du '''multicœurs asymétrique''' où les cœurs ne sont pas identiques. Et il en est de même sur les systèmes avec plusieurs processeurs : on parle de '''multiprocesseur symétrique''' si les processeurs sont identiques, '''multiprocesseur asymétrique''' s'ils sont différents.
Précisons ce que nous entendons par "cœurs différents" ou "identiques". Les processeurs Intel modernes utilisent deux types de cœurs différents : des cœurs P et des cœurs E. Le P est pour ''Performance'', le E est pour "Efficiency". Les deux ont le même jeu d'instruction : ce sont des processeurs x86. Par contre, ils ont des microarchitectures différentes. Et Intel n'est pas le seul à utiliser cette technique : ARM a fait pareil avec ses CPU d'architecture ''Big-little''. Il n'est pas clair si de telles organisation sont du multicœur symétrique ou asymétrique. Le jeu d'instruction est identique, sauf éventuellement pour certaines extension comme l'AVX. Les deux coeurs n'ont pas les mêmes performances, mais est-ce suffisant ? La terminologie n'est pas claire.
Un exemple de multicoeurs asymétrique est celui du processeur CELL de la console de jeu PS3. Il était conçu spécifiquement pour cette console. Il intègre un cœur principal POWER PC v5 et 8 cœurs qui servent de processeurs auxiliaires. Le processeur principal est appelé le PPE et les processeurs auxiliaires sont les SPE. Les SPE sont reliés à une mémoire locale (''local store'') de 256 kibioctets qui communique avec le processeur principal via un bus spécial. Cette fois-ci, les coprocesseurs sont intégrés dans le même processeur.
Les SPE communiquent avec la RAM principale via des contrôleurs DMA. Les SPE possèdent des instructions permettant de commander leur contrôleur DMA et c'est le seul moyen qu'ils ont pour récupérer des informations depuis la mémoire. Et c'est au programmeur de gérer tout ça ! C'est le processeur principal qui va envoyer aux SPE les programmes qu'ils doivent exécuter. Il délègue des calculs aux SPE en écrivant dans le local store du SPE et en lui ordonnant l’exécution du programme qu'il vient d'écrire.
[[File:Schema Cell.png|centre|vignette|upright=2|Architecture du processeur CELL de la PS3. Le PPE est le processeur principal, les SPE sont des processeurs auxiliaires qui comprennent : un ''local store'' noté LS, un processeur noté SXU, et un contrôleur DMA pour échanger des informations avec la mémoire principale.]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les architectures parallèles
| prevText=Les architectures parallèles
| next=Architectures multithreadées et Hyperthreading
| nextText=Architectures multithreadées et Hyperthreading
}}
</noinclude>
olwr71l16kdcdy1p32w35dfxxzvm4a3
Fonctionnement d'un ordinateur/Sommaire
0
69596
773294
772081
2026-09-27T15:46:25Z
Mewtow
31375
/* L'émission multiple */
773294
wikitext
text/x-wiki
__NOTOC__
* [[Fonctionnement d'un ordinateur/Introduction|Introduction]]
==Le codage des informations==
* [[Fonctionnement d'un ordinateur/L'encodage des données|L'encodage des données]]
* [[Fonctionnement d'un ordinateur/Le codage des nombres|Le codage des nombres]]
* [[Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur|Les codes de détection/correction d'erreur]]
==Les circuits électroniques==
* [[Fonctionnement d'un ordinateur/Les portes logiques|Les portes logiques]]
===Les circuits combinatoires===
* [[Fonctionnement d'un ordinateur/Les circuits combinatoires|Les circuits combinatoires]]
* [[Fonctionnement d'un ordinateur/Les circuits de masquage|Les circuits de masquage]]
* [[Fonctionnement d'un ordinateur/Les circuits de sélection|Les circuits de sélection]]
* [[Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs|Les circuits incrémenteurs/décrémenteurs]]
===Les circuits séquentiels===
* [[Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit|Les bascules : des mémoires de 1 bit]]
* [[Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones|Les circuits synchrones et asynchrones]]
* [[Fonctionnement d'un ordinateur/Les registres et mémoires adressables|Les registres et mémoires adressables]]
* [[Fonctionnement d'un ordinateur/Les compteurs et timers|Les compteurs et timers]]
* [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR|Les registres à décalage et les LSFR]]
===Les circuits de calcul et de comparaison===
* [[Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation|Les circuits de décalage et de rotation]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction|Les circuits pour l'addition et la soustraction]]
* [[Fonctionnement d'un ordinateur/Les circuits de comparaison|Les circuits de comparaison]]
* [[Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)|Les unités arithmétiques et logiques entières (simples)]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande|Les circuits pour l'addition multiopérande]]
* [[Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division|Les circuits pour la multiplication et la division]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit|Les circuits de calcul logique et bit à bit]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul flottant|Les circuits de calcul flottant]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques|Les circuits de calcul trigonométriques]]
===Les circuits intégrés à semi-conducteurs===
* [[Fonctionnement d'un ordinateur/Les transistors et portes logiques|Les transistors et portes logiques]]
* [[Fonctionnement d'un ordinateur/Les circuits intégrés|Les circuits intégrés]]
* [[Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus|L'interface électrique entre circuits intégrés et bus]]
==L'architecture d'un ordinateur==
* [[Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur|L'architecture de base d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La hiérarchie mémoire|La hiérarchie mémoire]]
* [[Fonctionnement d'un ordinateur/La performance d'un ordinateur|La performance d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques|La loi de Moore et les tendances technologiques]]
* [[Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur|Les techniques de réduction de la consommation électrique d'un processeur]]
==Les bus électroniques et la carte mère==
* [[Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS|La carte mère, chipset et BIOS]]
* [[Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)|Les bus et liaisons point à point (généralités)]]
* [[Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus|Les encodages spécifiques aux bus]]
* [[Fonctionnement d'un ordinateur/Les liaisons point à point|Les liaisons point à point]]
* [[Fonctionnement d'un ordinateur/Les bus électroniques|Les bus électroniques]]
* [[Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point|Quelques exemples de bus et de liaisons point à point]]
==Les mémoires RAM/ROM==
* [[Fonctionnement d'un ordinateur/Les différents types de mémoires|Les différents types de mémoires]]
* [[Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique|L'interface d'une mémoire électronique]]
* [[Fonctionnement d'un ordinateur/Le bus mémoire|Le bus mémoire]]
===La micro-architecture d'une mémoire adressable===
* [[Fonctionnement d'un ordinateur/Les cellules mémoires|Les cellules mémoires]]
* [[Fonctionnement d'un ordinateur/Le plan mémoire|Le plan mémoire]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire interne|Le contrôleur mémoire interne]]
* [[Fonctionnement d'un ordinateur/Mémoires évoluées|Les mémoires évoluées]]
===Les mémoires primaires===
* [[Fonctionnement d'un ordinateur/Les mémoires ROM|Les mémoires ROM : Mask ROM, PROM, EPROM, EEPROM, Flash]]
* [[Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones|Les mémoires SRAM synchrones]]
* [[Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)|Les mémoires RAM dynamiques (DRAM)]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire externe|Le contrôleur mémoire externe]]
===Les mémoires exotiques===
* [[Fonctionnement d'un ordinateur/Les mémoires associatives|Les mémoires associatives]]
* [[Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO|Les mémoires FIFO et LIFO]]
==Le processeur==
===L'architecture externe===
* [[Fonctionnement d'un ordinateur/Langage machine et assembleur|Langage machine et assembleur]]
* [[Fonctionnement d'un ordinateur/Les registres du processeur|Les registres du processeur]]
* [[Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme|Le modèle mémoire : alignement et boutisme]]
* [[Fonctionnement d'un ordinateur/Les modes d'adressage|Les modes d'adressage]]
* [[Fonctionnement d'un ordinateur/L'encodage des instructions|L'encodage des instructions]]
* [[Fonctionnement d'un ordinateur/Les jeux d'instructions|Les jeux d'instructions]]
* [[Fonctionnement d'un ordinateur/La pile d'appel et les fonctions|La pile d'appel et les fonctions]]
* [[Fonctionnement d'un ordinateur/Les interruptions et exceptions|Les interruptions et exceptions]]
===La micro-architecture===
* [[Fonctionnement d'un ordinateur/Les composants d'un processeur|Les composants d'un processeur]]
* [[Fonctionnement d'un ordinateur/Le chemin de données|Le chemin de données]]
* [[Fonctionnement d'un ordinateur/L'unité de chargement et le program counter|L'unité de chargement et le program counter]]
* [[Fonctionnement d'un ordinateur/L'unité de contrôle|L'unité de contrôle]]
* [[Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements|L'implémentation matérielle des branchements]]
===Les jeux d'instruction anciens, avant les registres généraux ou exotiques===
* [[Fonctionnement d'un ordinateur/Les architectures à accumulateur|Les architectures à accumulateur]]
* [[Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire|Les architectures à pile et mémoire-mémoire]]
* [[Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins|Les processeurs 8 bits et moins]]
===L'espace d'adressage du processeur et la multiprogrammation===
* [[Fonctionnement d'un ordinateur/L'espace d'adressage du processeur|L'espace d'adressage du processeur]]
* [[Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle|L'abstraction mémoire et la mémoire virtuelle]]
==Les entrées-sorties et périphériques==
* [[Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques|Les méthodes de synchronisation entre processeur et périphériques]]
* [[Fonctionnement d'un ordinateur/L'adressage des périphériques|L'adressage des périphériques]]
* [[Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension|Les périphériques et les cartes d'extension]]
==Les mémoires de stockage==
* [[Fonctionnement d'un ordinateur/Les mémoires de masse : généralités|Les mémoires de masse : généralités]]
* [[Fonctionnement d'un ordinateur/Les disques durs|Les disques durs]]
* [[Fonctionnement d'un ordinateur/Les solid-state drives|Les solid-state drives]]
* [[Fonctionnement d'un ordinateur/Les disques optiques|Les disques optiques]]
* [[Fonctionnement d'un ordinateur/Compléments sur les mémoires de masse|Compléments sur les mémoires de masse]]
==La ou les mémoires caches==
* [[Fonctionnement d'un ordinateur/Les mémoires cache|Les mémoires cache]]
* [[Fonctionnement d'un ordinateur/Le préchargement|Le préchargement]]
* [[Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer|Le ''Translation Lookaside Buffer'']]
==Le parallélisme d’instructions==
* [[Fonctionnement d'un ordinateur/Le pipeline|Le pipeline]]
===Les branchements et le ''front-end''===
* [[Fonctionnement d'un ordinateur/La prédiction de branchement|La prédiction de branchement]]
* [[Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions|Les optimisations du chargement des instructions]]
===Les pipelines multicycles simples===
* [[Fonctionnement d'un ordinateur/Les pipelines multicycles|Les pipelines multicycles]]
* [[Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions|L'émission dans l'ordre des instructions]]
* [[Fonctionnement d'un ordinateur/Le contournement (data forwarding)|Le contournement (data forwarding)]]
* [[Fonctionnement d'un ordinateur/Les premiers processeurs Intel|Les premiers processeurs Intel]]
===L’exécution dans le désordre===
* [[Fonctionnement d'un ordinateur/L'exécution dans le désordre|L'exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le renommage de registres|Le renommage de registres]]
* [[Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo|Annexe : Le scoreboarding et l'algorithme de Tomasulo]]
===Les optimisations des accès mémoire===
* [[Fonctionnement d'un ordinateur/La désambiguïsation mémoire|La désambiguïsation mémoire]]
* [[Fonctionnement d'un ordinateur/Le parallélisme mémoire|Le parallélisme mémoire]]
===L'émission multiple===
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires|Les processeurs superscalaires]]
* [[Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86|Les microarchitectures pour le x86 : haute performance]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : basse consommation|Les microarchitectures pour le x86 : basse consommation]]
* [[Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC|Les processeurs VLIW et EPIC]]
* [[Fonctionnement d'un ordinateur/Les architectures dataflow|Les architectures dataflow]]
==Les architectures parallèles==
* [[Fonctionnement d'un ordinateur/Les architectures parallèles|Les architectures parallèles]]
* [[Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs|Les architectures multiprocesseurs et multicœurs]]
* [[Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading|Les architectures multithreadées et Hyperthreading]]
* [[Fonctionnement d'un ordinateur/Les architectures à parallélisme de données|Les architectures à parallélisme de données]]
* [[Fonctionnement d'un ordinateur/La cohérence des caches|La cohérence des caches]]
* [[Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire|Les sections critiques et le modèle mémoire]]
==Annexes==
===Les nombres flottants : FPUs et coprocesseurs===
* [[Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87|Un exemple de jeu d'instruction : l'extension x87]]
* [[Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO|Les coprocesseurs : FPU et IO]]
===Les jeux d’instruction spécialisés===
* [[Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]]
* [[Fonctionnement d'un ordinateur/Les processeurs de traitement du signal|Les processeurs de traitement du signal]]
* [[Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement|Les architectures actionnées par déplacement]]
===Réseaux de neurones et accélérateurs d'IA===
* [[Fonctionnement d'un ordinateur/Les architectures systoliques|Les architectures systoliques]]
* [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]]
===Les autres annexes===
* [[Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation|L'accélération matérielle de la virtualisation]]
* [[Fonctionnement d'un ordinateur/Le matériel réseau|Le matériel réseau]]
* [[Fonctionnement d'un ordinateur/La tolérance aux pannes|La tolérance aux pannes]]
* [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires|Les ordinateurs de première génération : tubes à vide et mémoires]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires|Les ordinateurs à encodages non-binaires]]
* [[Fonctionnement d'un ordinateur/Les circuits réversibles|Les circuits réversibles]]
{{autocat}}
r2p0am00s8bo8sw78r2q2jp51y94717
773301
773294
2026-09-27T15:57:30Z
Mewtow
31375
/* L'émission multiple */
773301
wikitext
text/x-wiki
__NOTOC__
* [[Fonctionnement d'un ordinateur/Introduction|Introduction]]
==Le codage des informations==
* [[Fonctionnement d'un ordinateur/L'encodage des données|L'encodage des données]]
* [[Fonctionnement d'un ordinateur/Le codage des nombres|Le codage des nombres]]
* [[Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur|Les codes de détection/correction d'erreur]]
==Les circuits électroniques==
* [[Fonctionnement d'un ordinateur/Les portes logiques|Les portes logiques]]
===Les circuits combinatoires===
* [[Fonctionnement d'un ordinateur/Les circuits combinatoires|Les circuits combinatoires]]
* [[Fonctionnement d'un ordinateur/Les circuits de masquage|Les circuits de masquage]]
* [[Fonctionnement d'un ordinateur/Les circuits de sélection|Les circuits de sélection]]
* [[Fonctionnement d'un ordinateur/Les circuits incrémenteurs/décrémenteurs|Les circuits incrémenteurs/décrémenteurs]]
===Les circuits séquentiels===
* [[Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit|Les bascules : des mémoires de 1 bit]]
* [[Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones|Les circuits synchrones et asynchrones]]
* [[Fonctionnement d'un ordinateur/Les registres et mémoires adressables|Les registres et mémoires adressables]]
* [[Fonctionnement d'un ordinateur/Les compteurs et timers|Les compteurs et timers]]
* [[Fonctionnement d'un ordinateur/Les registres à décalage et les LSFR|Les registres à décalage et les LSFR]]
===Les circuits de calcul et de comparaison===
* [[Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation|Les circuits de décalage et de rotation]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction|Les circuits pour l'addition et la soustraction]]
* [[Fonctionnement d'un ordinateur/Les circuits de comparaison|Les circuits de comparaison]]
* [[Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)|Les unités arithmétiques et logiques entières (simples)]]
* [[Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande|Les circuits pour l'addition multiopérande]]
* [[Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division|Les circuits pour la multiplication et la division]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit|Les circuits de calcul logique et bit à bit]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul flottant|Les circuits de calcul flottant]]
* [[Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques|Les circuits de calcul trigonométriques]]
===Les circuits intégrés à semi-conducteurs===
* [[Fonctionnement d'un ordinateur/Les transistors et portes logiques|Les transistors et portes logiques]]
* [[Fonctionnement d'un ordinateur/Les circuits intégrés|Les circuits intégrés]]
* [[Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus|L'interface électrique entre circuits intégrés et bus]]
==L'architecture d'un ordinateur==
* [[Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur|L'architecture de base d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La hiérarchie mémoire|La hiérarchie mémoire]]
* [[Fonctionnement d'un ordinateur/La performance d'un ordinateur|La performance d'un ordinateur]]
* [[Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques|La loi de Moore et les tendances technologiques]]
* [[Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur|Les techniques de réduction de la consommation électrique d'un processeur]]
==Les bus électroniques et la carte mère==
* [[Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS|La carte mère, chipset et BIOS]]
* [[Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)|Les bus et liaisons point à point (généralités)]]
* [[Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus|Les encodages spécifiques aux bus]]
* [[Fonctionnement d'un ordinateur/Les liaisons point à point|Les liaisons point à point]]
* [[Fonctionnement d'un ordinateur/Les bus électroniques|Les bus électroniques]]
* [[Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point|Quelques exemples de bus et de liaisons point à point]]
==Les mémoires RAM/ROM==
* [[Fonctionnement d'un ordinateur/Les différents types de mémoires|Les différents types de mémoires]]
* [[Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique|L'interface d'une mémoire électronique]]
* [[Fonctionnement d'un ordinateur/Le bus mémoire|Le bus mémoire]]
===La micro-architecture d'une mémoire adressable===
* [[Fonctionnement d'un ordinateur/Les cellules mémoires|Les cellules mémoires]]
* [[Fonctionnement d'un ordinateur/Le plan mémoire|Le plan mémoire]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire interne|Le contrôleur mémoire interne]]
* [[Fonctionnement d'un ordinateur/Mémoires évoluées|Les mémoires évoluées]]
===Les mémoires primaires===
* [[Fonctionnement d'un ordinateur/Les mémoires ROM|Les mémoires ROM : Mask ROM, PROM, EPROM, EEPROM, Flash]]
* [[Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones|Les mémoires SRAM synchrones]]
* [[Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)|Les mémoires RAM dynamiques (DRAM)]]
* [[Fonctionnement d'un ordinateur/Contrôleur mémoire externe|Le contrôleur mémoire externe]]
===Les mémoires exotiques===
* [[Fonctionnement d'un ordinateur/Les mémoires associatives|Les mémoires associatives]]
* [[Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO|Les mémoires FIFO et LIFO]]
==Le processeur==
===L'architecture externe===
* [[Fonctionnement d'un ordinateur/Langage machine et assembleur|Langage machine et assembleur]]
* [[Fonctionnement d'un ordinateur/Les registres du processeur|Les registres du processeur]]
* [[Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme|Le modèle mémoire : alignement et boutisme]]
* [[Fonctionnement d'un ordinateur/Les modes d'adressage|Les modes d'adressage]]
* [[Fonctionnement d'un ordinateur/L'encodage des instructions|L'encodage des instructions]]
* [[Fonctionnement d'un ordinateur/Les jeux d'instructions|Les jeux d'instructions]]
* [[Fonctionnement d'un ordinateur/La pile d'appel et les fonctions|La pile d'appel et les fonctions]]
* [[Fonctionnement d'un ordinateur/Les interruptions et exceptions|Les interruptions et exceptions]]
===La micro-architecture===
* [[Fonctionnement d'un ordinateur/Les composants d'un processeur|Les composants d'un processeur]]
* [[Fonctionnement d'un ordinateur/Le chemin de données|Le chemin de données]]
* [[Fonctionnement d'un ordinateur/L'unité de chargement et le program counter|L'unité de chargement et le program counter]]
* [[Fonctionnement d'un ordinateur/L'unité de contrôle|L'unité de contrôle]]
* [[Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements|L'implémentation matérielle des branchements]]
===Les jeux d'instruction anciens, avant les registres généraux ou exotiques===
* [[Fonctionnement d'un ordinateur/Les architectures à accumulateur|Les architectures à accumulateur]]
* [[Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire|Les architectures à pile et mémoire-mémoire]]
* [[Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins|Les processeurs 8 bits et moins]]
===L'espace d'adressage du processeur et la multiprogrammation===
* [[Fonctionnement d'un ordinateur/L'espace d'adressage du processeur|L'espace d'adressage du processeur]]
* [[Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle|L'abstraction mémoire et la mémoire virtuelle]]
==Les entrées-sorties et périphériques==
* [[Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques|Les méthodes de synchronisation entre processeur et périphériques]]
* [[Fonctionnement d'un ordinateur/L'adressage des périphériques|L'adressage des périphériques]]
* [[Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension|Les périphériques et les cartes d'extension]]
==Les mémoires de stockage==
* [[Fonctionnement d'un ordinateur/Les mémoires de masse : généralités|Les mémoires de masse : généralités]]
* [[Fonctionnement d'un ordinateur/Les disques durs|Les disques durs]]
* [[Fonctionnement d'un ordinateur/Les solid-state drives|Les solid-state drives]]
* [[Fonctionnement d'un ordinateur/Les disques optiques|Les disques optiques]]
* [[Fonctionnement d'un ordinateur/Compléments sur les mémoires de masse|Compléments sur les mémoires de masse]]
==La ou les mémoires caches==
* [[Fonctionnement d'un ordinateur/Les mémoires cache|Les mémoires cache]]
* [[Fonctionnement d'un ordinateur/Le préchargement|Le préchargement]]
* [[Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer|Le ''Translation Lookaside Buffer'']]
==Le parallélisme d’instructions==
* [[Fonctionnement d'un ordinateur/Le pipeline|Le pipeline]]
===Les branchements et le ''front-end''===
* [[Fonctionnement d'un ordinateur/La prédiction de branchement|La prédiction de branchement]]
* [[Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions|Les optimisations du chargement des instructions]]
===Les pipelines multicycles simples===
* [[Fonctionnement d'un ordinateur/Les pipelines multicycles|Les pipelines multicycles]]
* [[Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions|L'émission dans l'ordre des instructions]]
* [[Fonctionnement d'un ordinateur/Le contournement (data forwarding)|Le contournement (data forwarding)]]
* [[Fonctionnement d'un ordinateur/Les premiers processeurs Intel|Les premiers processeurs Intel]]
===L’exécution dans le désordre===
* [[Fonctionnement d'un ordinateur/L'exécution dans le désordre|L'exécution dans le désordre]]
* [[Fonctionnement d'un ordinateur/Le renommage de registres|Le renommage de registres]]
* [[Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo|Annexe : Le scoreboarding et l'algorithme de Tomasulo]]
===Les optimisations des accès mémoire===
* [[Fonctionnement d'un ordinateur/La désambiguïsation mémoire|La désambiguïsation mémoire]]
* [[Fonctionnement d'un ordinateur/Le parallélisme mémoire|Le parallélisme mémoire]]
===L'émission multiple===
* [[Fonctionnement d'un ordinateur/Les processeurs superscalaires|Les processeurs superscalaires]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance|Les microarchitectures pour le x86 : haute performance]]
* [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : basse consommation|Les microarchitectures pour le x86 : basse consommation]]
* [[Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC|Les processeurs VLIW et EPIC]]
* [[Fonctionnement d'un ordinateur/Les architectures dataflow|Les architectures dataflow]]
==Les architectures parallèles==
* [[Fonctionnement d'un ordinateur/Les architectures parallèles|Les architectures parallèles]]
* [[Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs|Les architectures multiprocesseurs et multicœurs]]
* [[Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading|Les architectures multithreadées et Hyperthreading]]
* [[Fonctionnement d'un ordinateur/Les architectures à parallélisme de données|Les architectures à parallélisme de données]]
* [[Fonctionnement d'un ordinateur/La cohérence des caches|La cohérence des caches]]
* [[Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire|Les sections critiques et le modèle mémoire]]
==Annexes==
===Les nombres flottants : FPUs et coprocesseurs===
* [[Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87|Un exemple de jeu d'instruction : l'extension x87]]
* [[Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO|Les coprocesseurs : FPU et IO]]
===Les jeux d’instruction spécialisés===
* [[Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]]
* [[Fonctionnement d'un ordinateur/Les processeurs de traitement du signal|Les processeurs de traitement du signal]]
* [[Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement|Les architectures actionnées par déplacement]]
===Réseaux de neurones et accélérateurs d'IA===
* [[Fonctionnement d'un ordinateur/Les architectures systoliques|Les architectures systoliques]]
* [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]]
===Les autres annexes===
* [[Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation|L'accélération matérielle de la virtualisation]]
* [[Fonctionnement d'un ordinateur/Le matériel réseau|Le matériel réseau]]
* [[Fonctionnement d'un ordinateur/La tolérance aux pannes|La tolérance aux pannes]]
* [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires|Les ordinateurs de première génération : tubes à vide et mémoires]]
* [[Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires|Les ordinateurs à encodages non-binaires]]
* [[Fonctionnement d'un ordinateur/Les circuits réversibles|Les circuits réversibles]]
{{autocat}}
81lt8mesft1wdqfgtqmero7lfvfia6g
Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance
0
82608
773276
773038
2026-09-27T13:30:05Z
Mewtow
31375
/* Les processeurs x86 d'Intel, la lignée principale */
773276
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc.
Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
Les architectures '''Ice Lake''' et '''Tiger Lake''' passent de quadruple émission à la pentuple émission. Par contre, le processeur utilise toujours 4 décodeurs. Mais les micro-opérations étant émises depuis le cache de micro-opérations, ce n'est pas un problème pour la pentuple émission. Le processeur peut parfaitement émettre 5 micro-opérations en même temps, si elles sont lues depuis le cache de micro-opérations. Là encore, on voit à quel point le cache de micro-opération découple ce qu'il y avant de ce qu'il y a après.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel,n les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
8l2djijh9yg0sh8bbzqu2maudkdrynh
773277
773276
2026-09-27T13:30:35Z
Mewtow
31375
/* Les microarchitectures "Cove" d'Intel */
773277
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc.
Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
Les architectures '''Ice Lake''' et '''Tiger Lake''' passent de quadruple émission à la pentuple émission. Par contre, le processeur utilise toujours 4 décodeurs. Mais les micro-opérations étant émises depuis le cache de micro-opérations, ce n'est pas un problème pour la pentuple émission. Le processeur peut parfaitement émettre 5 micro-opérations en même temps, si elles sont lues depuis le cache de micro-opérations. Là encore, on voit à quel point le cache de micro-opération découple ce qu'il y avant de ce qu'il y a après.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
b7na8abdqoh87m81n14rqdbqw5xmprt
773278
773277
2026-09-27T13:39:00Z
Mewtow
31375
/* Les microarchitectures Sandy Bridge and Ivy Bridge */
773278
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
hr7fuoltdq97ilr8ca4svf29zw7jnjp
773279
773278
2026-09-27T13:43:19Z
Mewtow
31375
/* Les microarchitectures "Cove" d'Intel */
773279
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
n5zyqofp5ufewr30xp84vpc6qig9mw2
773280
773279
2026-09-27T14:18:25Z
Mewtow
31375
/* Le système d'exceptions flottantes de l'Atom */
773280
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre, ce qui le rendait bien plus performant que le Pentium, sur le papier. Par contre, il avait mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Sa FPU était peu performante, proche de celle du 5x86. Les applications développées pour le Pentium, qui répartissaient au miuex leurs calculs sentre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
0im76jsjgfbnp86ltg1k3fdrqgmjoz8
773281
773280
2026-09-27T14:19:03Z
Mewtow
31375
/* Les microarchitectures x86 de Cyrix et Via */
773281
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Les architectures superscalaires de Cyrix===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre, ce qui le rendait bien plus performant que le Pentium, sur le papier. Par contre, il avait mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Sa FPU était peu performante, proche de celle du 5x86. Les applications développées pour le Pentium, qui répartissaient au miuex leurs calculs sentre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
cqjx21zpni7ot3jvj0wwks6sy5la3b8
773282
773281
2026-09-27T14:20:22Z
Mewtow
31375
/* Les architectures superscalaires de Cyrix */
773282
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Les architectures superscalaires de Cyrix===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre, ce qui le rendait bien plus performant que le Pentium, sur le papier. Par contre, il avait mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Sa FPU était peu performante, proche de celle du 5x86. Les applications développées pour le Pentium, qui répartissaient au miuex leurs calculs sentre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
kdiabiboa2kvn6pyhe03fofx6mgari2
773283
773282
2026-09-27T14:23:14Z
Mewtow
31375
/* Les architectures superscalaires de Cyrix */
773283
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Les architectures superscalaires de Cyrix===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre, ce qui le rendait bien plus performant que le Pentium, sur le papier. Par contre, il avait mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Sa FPU était peu performante, proche de celle du 5x86. Les applications développées pour le Pentium, qui répartissaient au miuex leurs calculs sentre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
8vnty18twuhedyparmxtgidfzgpy47y
773284
773283
2026-09-27T14:24:55Z
Mewtow
31375
/* Les architectures superscalaires de Cyrix */
773284
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Les architectures superscalaires de Cyrix===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre, ce qui le rendait bien plus performant que le Pentium, sur le papier. Par contre, il avait mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Sa FPU était peu performante, proche de celle du 5x86. Les applications développées pour le Pentium, qui répartissaient au miuex leurs calculs sentre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
f8v1xwddh9wtkc3b8hmjoy7qq4s1hz6
773285
773284
2026-09-27T14:34:13Z
Mewtow
31375
/* Les architectures superscalaires de Cyrix */
773285
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Les architectures superscalaires de Cyrix===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
rzoee6vlyyaz6qfcoqu6dedrxmqsx2l
773286
773285
2026-09-27T14:34:49Z
Mewtow
31375
/* Les architectures superscalaires de Cyrix */
773286
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
k95t03i97ri4kzhyrdv8ckar659fokl
773287
773286
2026-09-27T14:44:34Z
Mewtow
31375
/* Le Cyrix 6x86 : une architecture superscalaire avancée */
773287
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
dqptqhksny9yy3ap28q8kyce18bgivv
773288
773287
2026-09-27T15:03:13Z
Mewtow
31375
/* Les microarchitectures x86 de Cyrix et Via */
773288
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
Par la suite, Via sortit le '''Via Nano'''en2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD).
[[File:VIA Nano Architecture Blockdiagram.jpg|centre|vignette|upright=1.5|VIA Nano Architecture.]]
Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
i53hgeh01jkgbxpnyxijkphpgob1yuv
773289
773288
2026-09-27T15:17:24Z
Mewtow
31375
/* Les processeurs de marque Via */
773289
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano'''en2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
3oppz6xn16tktuzi2fsb72xe84yqc0w
773290
773289
2026-09-27T15:22:13Z
Mewtow
31375
/* Les processeurs de marque Via */
773290
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
kagmmdlprnxnk2zu7lw72bk9146gu5i
773291
773290
2026-09-27T15:30:56Z
Mewtow
31375
/* Les processeurs de marque Via */
773291
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
6tf800xesa1fozzqo5hmbuag8zgitcw
773292
773291
2026-09-27T15:42:45Z
Mewtow
31375
/* Les microarchitectures x86 de Cyrix et Via */
773292
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
n5zyqofp5ufewr30xp84vpc6qig9mw2
773293
773292
2026-09-27T15:42:53Z
Mewtow
31375
/* Le replay pipeline */
773293
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
68wu0pdtwgg5zictsolusmjipoqhjoe
773296
773293
2026-09-27T15:55:51Z
Mewtow
31375
/* Les processeurs Atom d'Intel, de microarchitecture Bonnell */ Déplacement autre chapitre
773296
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
e13oi6zdr68omuy688stausycow1h4d
773297
773296
2026-09-27T15:56:57Z
Mewtow
31375
Mewtow a déplacé la page [[Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86]] vers [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance]] : Pour harmoniser avec le chapitre suivant, créé aujourd'hui
773296
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Une étude des micro-architectures superscalaires x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
e13oi6zdr68omuy688stausycow1h4d
773304
773297
2026-09-27T16:12:07Z
Mewtow
31375
/* Une étude des micro-architectures superscalaires x86 d'AMD */
773304
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel, la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
cj7cq7l8vo59d0kifl5h7yk1565f9pu
773305
773304
2026-09-27T16:12:23Z
Mewtow
31375
/* Les processeurs x86 d'Intel, la lignée principale */
773305
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps. Et il faut noter que même les cœurs basse performance sont dans ce cas. Pour rappel, les CPU Intel modernes regroupent deux types de cœurs : les cœurs P et les cœurs E. Les cœurs P sont des cœurs haute performance, basés sur les microarchitectures Cove, optimisées pour la performance. Les cœurs E, quant à eux, utilisent une microarchitecture différente, conçue pour économiser de l'énergie et avoir une consommation réduite, au prix de performances réduites. Pourtant, même les coeurs E utilisent une superscalarité large !
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
b5nxvl0p7f1hhc20cz4rzlx26ovquto
773307
773305
2026-09-27T16:17:28Z
Mewtow
31375
/* Les microarchitectures "Cove" d'Intel */
773307
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
orjg8sp36vq17yytexe9zlg3ju1jmor
773374
773307
2026-09-27T22:00:33Z
Mewtow
31375
/* Un petit historique des processeurs x86 superscalaires */
773374
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacée les instructions flottantes progressivement.
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des isntructions entières. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Plus haut, j'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|vignette|Registres AVX.]]
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
d4o7g5syvan530ff24gjm2ykbaq63sj
773375
773374
2026-09-27T22:06:13Z
Mewtow
31375
/* Un aparté sur la FPU et les instructions SIMD */
773375
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacée les instructions flottantes progressivement.
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des isntructions entières. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Plus haut, j'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement.
Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
5ftoa7mptmgo529ef1t22ve58wcprqq
773377
773375
2026-09-27T22:23:05Z
Mewtow
31375
/* Le jeu d'instruction x86 pose des problèmes pour la superscalarité */
773377
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de ces deux articles de blog, qui donnent chacun une opinion différent sur la situation :
* [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die]
* [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacée les instructions flottantes progressivement.
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des isntructions entières. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Plus haut, j'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement.
Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
o7q5ivzqq6bv71yxalyejqljjyelk5m
773378
773377
2026-09-27T22:24:10Z
Mewtow
31375
/* Le jeu d'instruction x86 pose des problèmes pour la superscalarité */
773378
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacée les instructions flottantes progressivement.
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des isntructions entières. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Plus haut, j'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement.
Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
n5rawb1tm9qlkeldp6wcnh76ukqxty4
773379
773378
2026-09-27T22:30:37Z
Mewtow
31375
/* Un aparté sur la FPU et les instructions SIMD */
773379
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
Les autres instructions SIMD, et elles sont nombreuses, sont souvent implémentées dans une unité SIMD annexe.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports récents sont chacun reliés à une ALU entière. L'additionneur flottant est connecté au port 1, alors que le multiplieur/diviseur flottante est connecté au port 0. Le fait de mettre les deux sur des ports différents permet d'émettre une addition et une multiplication flottant simultanément. Le multiplieur entier est relié au second port d'émission, celui sur lequel se trouve l'additionneur flottant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
l7oiqnkr4tne5rn96ihumze6cphrv29
773380
773379
2026-09-27T22:33:25Z
Mewtow
31375
/* La microarchitecture Core */
773380
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
Les autres instructions SIMD, et elles sont nombreuses, sont souvent implémentées dans une unité SIMD annexe.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
cbl3u0jdm89jc7lwvlp0d5qcofthrzt
773383
773380
2026-09-27T22:51:49Z
Mewtow
31375
/* Un aparté sur la FPU et les instructions SIMD */
773383
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
3dqph0kch6cw9vv4ux1wy051rjekabl
773386
773383
2026-09-27T22:58:47Z
Mewtow
31375
/* Un aparté sur la FPU et les instructions SIMD */
773386
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
===Un aparté sur la FPU et les instructions SIMD===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Il est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
ieq9hdxtdsmogus2lxkbfdx6exe4j96
773387
773386
2026-09-27T23:08:14Z
Mewtow
31375
/* Généralités sur les CPU x86 superscalaires */
773387
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes.
Notamment, cela posait problème pour le renommage de registres. La conséquence est que les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Les instructions SIMD des processerus x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
hlz2wywjduut8xrpmcekn71jb8fa37j
773388
773387
2026-09-27T23:08:33Z
Mewtow
31375
/* Les instructions SIMD des processerus x86 */
773388
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
==Généralités sur les CPU x86 superscalaires==
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
===Le jeu d'instruction x86 pose des problèmes pour la superscalarité===
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes.
Notamment, cela posait problème pour le renommage de registres. La conséquence est que les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
5zioqoijsr2r4uzv3nw8a24hls6tjhr
773389
773388
2026-09-27T23:14:51Z
Mewtow
31375
/* Généralités sur les CPU x86 superscalaires */
773389
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. Nous allons d'abord voir que le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
==Le jeu d'instruction x86 pose des problèmes pour la superscalarité==
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
==La FPU et les unités SIMD sont à part du reste==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
55r258pi9gdje6mj13iwwc8tje8i2cw
773390
773389
2026-09-27T23:15:23Z
Mewtow
31375
773390
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
==Le jeu d'instruction x86 pose des problèmes pour la superscalarité==
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==La FPU et les unités SIMD sont à part du reste==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
7m6qezastp89v4pc1c0b3ar0pjbbsl7
773391
773390
2026-09-27T23:15:32Z
Mewtow
31375
/* Le jeu d'instruction x86 pose des problèmes pour la superscalarité */
773391
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre. La raison est que l'exécution dans le désordre est arrivé après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==La FPU et les unités SIMD sont à part du reste==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
43x3f4mnh1hkk4a2pmf4i5jtw83znb5
773392
773391
2026-09-27T23:16:01Z
Mewtow
31375
773392
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité. Et ces problèmes posent des contraintes assez fortes, avec lesquelles les concepteurs de processeurs dovient faire avec. Nous poursuivrons ensuite par un historique des processeurs Intel et AMD, histoire de donner un peu de contexte aux processeurs que nous allons étudier.
Une difficulté de l'architecture x86 est qu'il s'agit d'une architecture CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==La FPU et les unités SIMD sont à part du reste==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
g5k6z4y97w0mug5doiwdkh16hwvrpzl
773393
773392
2026-09-27T23:16:47Z
Mewtow
31375
773393
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==La FPU et les unités SIMD sont à part du reste==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
4oqemmpkq69agniwxdvxk1zocfvrzxt
773394
773393
2026-09-27T23:21:07Z
Mewtow
31375
/* La FPU et les unités SIMD sont à part du reste */
773394
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
==Les processeurs x86 d'Intel : la lignée principale==
Pour commencer, nous allons voir les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont plus simples que tous les autres. Il s'agit en effet de processeurs superscalaires, mais sans exécution dans le désordre. L’absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre.
Le successeur du Pentium 2 a intégré l'exécution dans le désordre, ce qui fait que les Pentium 1 et 2 sont les seuls processeurs superscalaires ''in-order''. Pour la concurrence, AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre. A la rigueur, il y a bien les processeurs Atom de la lignée secondaire d'Intel. Cependant, même s'ils sont bien des CPU ''in-order'', leur architecture est assez compliquée. En comparaison, le Pentium est une vieille architecture, qui se débrouillait avec peu de transistors et était donc bien plus simple que celle de l'Atom.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
alay2c0tlq5d1s7clxgf8une8v8p8x1
773395
773394
2026-09-27T23:25:41Z
Mewtow
31375
773395
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
===Un petit historique des processeurs x86 superscalaires===
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
jndm6le5i1d6fcnap3s32a1vpyljykf
773396
773395
2026-09-27T23:26:20Z
Mewtow
31375
/* Un petit historique des processeurs x86 superscalaires */
773396
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : microarchitectures==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
hwyh0b6vql8remr5lca9cola8y245l1
773397
773396
2026-09-27T23:26:33Z
Mewtow
31375
/* Les processeurs x86 d'AMD : microarchitectures */
773397
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===Les micro-architectures ZEN d'AMD===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Mais du fait de l'utilisation de techniques de multithreading matériel que nous n'avons pas encore abordé, nous ne pouvons pas en parler ici.
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
5vf1q2145mzplt974pfc5lqi3uue84v
773399
773397
2026-09-27T23:53:35Z
Mewtow
31375
/* Les micro-architectures ZEN d'AMD */
773399
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops et l'unité de renommage le sont ! Le seul truc qui est dupliqué est la file d'instruction, présente en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
jfn9uv7v28p7sh4vzr5l5125wz6r5lz
773402
773399
2026-09-27T23:59:15Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773402
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
epa8kkm7yfpr29oj7207w9ejx4n6zyz
773403
773402
2026-09-28T00:04:00Z
Mewtow
31375
773403
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
jngqlzpnpva822bn2hvigl4oveb6ail
773404
773403
2026-09-28T00:07:55Z
Mewtow
31375
/* Un aparté sur la FPU des processeurs x86 */
773404
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
ba5p54ui49wdohrqp89vbui31ar12aj
773405
773404
2026-09-28T00:09:41Z
Mewtow
31375
/* Un aparté sur la FPU des processeurs x86 */
773405
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
ceo8y05jv6f81b4rllofnzc9f8g4j5n
773406
773405
2026-09-28T00:18:33Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773406
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
Au-delà de ce partage entre coeurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l'execution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
1ul857hsnnwegwug07m7f2hirlffsrv
773407
773406
2026-09-28T00:20:58Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773407
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
Au-delà de ce partage entre coeurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l'execution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
afa8n26fcgh5n4beakxnoe85ychld0k
773408
773407
2026-09-28T00:24:46Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773408
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
Au-delà de ce partage entre coeurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l'execution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa satation de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
o8ptd5pz86ad9bbsrisbg1xqj5eescu
773409
773408
2026-09-28T00:25:42Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773409
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre coeurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l'execution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa satation de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
sfh0zl3pgv58ug054odpbyan7lhllui
773410
773409
2026-09-28T00:31:09Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773410
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances. Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
j9jvjdaizqgqu0knb2qaedlxip46v65
773413
773410
2026-09-28T00:45:02Z
Mewtow
31375
/* La micro-architecture Bulldozer et ses dérivés */
773413
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
c211ilycd0ohjfgemrfxwxhnidjvip9
773415
773413
2026-09-28T01:23:20Z
Mewtow
31375
/* Un aparté sur la FPU des processeurs x86 */
773415
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
En général, la FPU est précédée par une station de réservation unique (ou par une fenêtre d'instruction sur certains CPU). Il y a souvent un port d'émission pour l'additionneur flottant et un autre pour le multiplieurs, sauf pour les tout premiers modèles d'Intel et AMD. Il y aussi souvent un port pour les instructions FSTORE, les écritures flottantes. Les ports en question servent à transmettre les données à écrire vers l'unité mémoire, ainsi que l'adresse mémoire associée.
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
fuyquq8bxyk0h74tpnhp5nchvfultjc
773416
773415
2026-09-28T01:26:16Z
Mewtow
31375
/* Un aparté sur la FPU des processeurs x86 */
773416
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
En général, la FPU est précédée par une station de réservation unique (ou par une fenêtre d'instruction sur certains CPU). Il y a souvent un port d'émission pour l'additionneur flottant et un autre pour le multiplieurs, sauf pour les tout premiers modèles d'Intel et AMD. Il y aussi souvent un port pour les instructions FSTORE, les écritures flottantes. Les ports en question servent à transmettre les données à écrire vers l'unité mémoire, ainsi que l'adresse mémoire associée.
{|class="wikitable"
|+ Station de réservation flottante
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3
|-
| FADD (additionneur flottant) || FMUL (multiplication flottante || FSTORE
|}
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel : des stations de réservation centralisées==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
9kzcji0anfucexv0i78lujeer788acq
773417
773416
2026-09-28T01:28:57Z
Mewtow
31375
/* Les processeurs x86 d'Intel : des stations de réservation centralisées */
773417
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
En général, la FPU est précédée par une station de réservation unique (ou par une fenêtre d'instruction sur certains CPU). Il y a souvent un port d'émission pour l'additionneur flottant et un autre pour le multiplieurs, sauf pour les tout premiers modèles d'Intel et AMD. Il y aussi souvent un port pour les instructions FSTORE, les écritures flottantes. Les ports en question servent à transmettre les données à écrire vers l'unité mémoire, ainsi que l'adresse mémoire associée.
{|class="wikitable"
|+ Station de réservation flottante
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3
|-
| FADD (additionneur flottant) || FMUL (multiplication flottante || FSTORE
|}
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD : des stations de réservation décentralisées==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
eqfrnz677yk1e8cw6nzxwpet3tbhtk0
773418
773417
2026-09-28T01:30:48Z
Mewtow
31375
/* Les processeurs x86 d'AMD : des stations de réservation décentralisées */
773418
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
En général, la FPU est précédée par une station de réservation unique (ou par une fenêtre d'instruction sur certains CPU). Il y a souvent un port d'émission pour l'additionneur flottant et un autre pour le multiplieurs, sauf pour les tout premiers modèles d'Intel et AMD. Il y aussi souvent un port pour les instructions FSTORE, les écritures flottantes. Les ports en question servent à transmettre les données à écrire vers l'unité mémoire, ainsi que l'adresse mémoire associée.
{|class="wikitable"
|+ Station de réservation flottante
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3
|-
| FADD (additionneur flottant) || FMUL (multiplication flottante || FSTORE
|}
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Une différence entre les microarchitectures AMD et Intel est qu'Intel préfére utiliser une fenêtre d'instruction centralisée, alors qu'AMD préfére des stations des fenêtres d'instruction décentralisées. Une autre différence est que les unités flottantes/SIMD sont bien séparées des unités entières, elles ont des ports bien séparés. Intel, en comparaison, préfére mettre une ALU et une unité FPU/SIMD sur le même port d'émission.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
2arvd4iwemiu67h4qp580gu75n20u0h
773419
773418
2026-09-28T01:33:35Z
Mewtow
31375
/* Les processeurs x86 d'AMD */
773419
wikitext
text/x-wiki
Dans ce chapitre, nous allons étudier des exemples de processeurs x86, ceux présents dans nos PC. Nous n'allons pas voir les anciens processeurs comme le 286, le 386 ou le 486. Nous allons commencer avec le Pentium 1, et les processeurs commerciaux qui ont suivis. Tous les processeurs que nous allons voir dans ce chapitre sont des processeurs superscalaires. De fait, ce n'est pas pour rien si ce chapitre se situe après le chapitre sur les processeurs superscalaires. Par contre, nous allons voir que certains n'ont pas d'exécution dans le désordre, car elle est arrivée après la superscalarité.
Le jeu d'instruction x86 pose quelques problèmes pour la superscalarité, car c'est un jeu d'instruction CISC, avec tous les défauts que ça implique. Un jeu d'instruction CISC a en effet de nombreuses propriétés qui collent mal avec l'émission multiple, avec la '''superscalarité'''. Il y en a plusieurs, certaines impactent le chargement des instructions, d'autres leur décodage, d'autres l'exécution, etc.
Premièrement, les instructions sont de longueur variable, entre 1 et 15 octets, ce qui complique leur chargement et leur décodage. En pratique, les processeurs chargent un bloc de 32 à 64 octets, et découpent celui-ci en plusieurs instructions. La conséquence est que l'usage d'instructions trop longues peut poser problème. Imaginez qu'un processeur charge un bloc de 16 octets et que celui-ci ne contienne qu'une seule instruction : on ne profite pas de la superscalarité.
Deuxièmement, une partie des instructions est microcodée, faute de mieux. Et cela pose de sérieux challenges pour l'implémentation des décodeurs. Dupliquer le microcode demanderait trop de transistors, ce qui fait que ce n'est pas fait. À la place, il n'y a qu'un seul microcode, ce qui fait que l'on ne peut pas décoder plusieurs instructions microcodées en même temps. Il est cependant possible de profiter de la superscalarité, en décodant une instruction microcodée en parallèle d'autres instructions non-microcodées. Et heureusement, ce cas est de loin le plus fréquent, il est rare que plusieurs instructions microcodées se suivent.
Troisièmement, la présence d'instructions ''load-up'', qui lisent un opérande en mémoire, peut poser problème, mais est aussi source d'optimisations assez intéressantes. En théorie, une instruction ''load-op'' est décodée en deux micro-opération : une pour lire d'opérande en RAM, l'autre pour faire l'opération arithmétique. Sauf que les processeurs x86 modernes optimisent la gestion des instructions ''load-up''. Par exemple, les premiers processeurs Atom géraient des micro-opérations de type ''load-up'', directement dans le chemin de données !
D'autres processeurs utilisent la technique de la '''micro-fusion''' pour retarder le décodage réel des instructions ''load-up'' assez loin dans le pipeline. Avec eux, une instruction ''load-op'' est décodée en une seule "macro-opération", qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. L'avantage est qu'une macro-opération ne prend qu'une seule entrée dans le tampon de ré-ordonnancement, la fenêtre d'instruction, la file de micro-opération, et les autres structures similaires.
Il faut cependant noter qu'avoir des instructions complexes peut avoir des avantages sur les processeurs superscalaires à exécution dans le désordre. Les avantages sont liés au fait que certaines instructions complexes peuvent être implémentées avec des circuits dédiés, plutot qu'avec une suite de µops ou d'instructions. Les performances sont alors meilleures car une grosse instruction complexe devient une seule µops, ce qui aide le renommage de registres, prend moins de place dans le ROB ou les stations de réservation, etc.
: Pour avoir une petite idée des avantages et inconvénients, je conseille la lecture de cet articles de blog : [https://chipsandcheese.com/p/why-x86-doesnt-need-to-die Why x86 Doesn’t Need to Die]. Il s'agit d'une réponse à cet autre article, plus court : [https://hackaday.com/2024/03/21/why-x86-needs-to-die/ Why X86 Needs To Die].
Avant de voir chaque processeur indépendamment des autres, nous allons devoir aborder quelques généralités. précisons cependant une chose : dans ce chapitre, nous ne parlerons pas des unités de prédiction de branchement, ni de la hiérarchie des caches. Et les raisons sont différentes entre les eux.
Les unités de prédiction de branchement ont été discutées dans le chapitre sur la prédiction de branchement. De plus, de nombreux processeurs différents utilisent des unités de prédiction de branchement qui ont des algorithmes similaires. Mais la raison principale est que l'unité de prédiction de branchement est complétement détachée du reste du pipeline, cache d'instruction excepté sur certaines microarchitectures. N'importe quelle unité de branchement peut s'adapter à n'importe quel pipeline/microarchitecture, tant que ses ''itmings'' et délais en cycles d'horloge tiennent la route. Il en est plus ou moins de même pour la hiérarchie de caches. Me système mémoire est assez indépendant du reste de la microarchitecture.
==Introduction : FPU, SIMD et historique==
Dans ce qui va suivre, vous allez rapidement remarquer quelque chose d'assez évident. Les processeurs x86 ont en quelque sorte deux "pipelines" séparés, assez différents les unes des autres, qui ont évolués en parallèle. D'un côté, il y a le "pipeline" entier/mémoire, qui exécute les opérations entières et les µops mémoire. Il regroupe des ALU entières, de AGU (unité de calcul d'adresse) et les unités mémoire LOAD/STORE. Il communique directement avec la ''Load/Store Queue'', car c'est là dedans que se font les calculs d'adresse. A l'opposé, les calculs flottants sont réalisés dans des pipelines séparés, sauf sur quelques processeurs qui font exception, comme les microarchitectures Core. Voyons pourquoi.
===Un aparté sur la FPU des processeurs x86===
Les processeurs x86 oint acquis une FPU assez rapidement dans l'histoire. Les processeurs 286 et 386 n'avaient pas de FPU, mais pouvaient faire appel à un coprocesseur spécialisé dans les calculs flottants : le '''coprocesseur x87'''. Le coprocesseur x87 intégrait 8 registres flottants et supportait 8quelques instructions flottante, comme FADD pour l'addition, FSUB pour la soustraction, FMUL pour la multiplication, FDIV pour la division, etc.
Le coprocesseur x87 a été intégré dans le processeur assez rapidement, dès le 486. Pour cela, le 486 intégrait une FPU et les 8 registres flottants. Les instructions FADD, SUB, FMUL, et quelques autres, ont aussi été ajoutées au jeu d'instruction de base du 486. Elles formaient une '''extension du jeu d'instruction x86''', à savoir des paquets d'instructions qui n'étaient pas dans le jeu d’instruction de base, mais qui ont été rajoutés par la suite et sont devenus des standards.
Détail extraordinairement important : le coprocesseur x87 intégrait aussi 8 registres, avec une organisation très spéciale. Les registres n'étaient pas "adressables" avec des noms de registres, ils étaient à la place organisé en pile d'opérande. Vous vous rappelez peut-être que, dans le chapitre "les machines à pile et mémoire-mémoire", j'ai parlé des processeurs avec une pile d'opérande. Le coprocesseur x87 en était un. Et cela posait pas mal de problème pour rendre les opérations flottantes performantes. Notamment, cela posait problème pour le renommage de registres.
Le fait que les registres flottants et généraux soient séparés fait qu'ils sont souvent renommés à part, dans deux unités e renommage différentes. Les registres flottants ont été les premiers à être renommés. De nombreux processeurs renommaient les registres flottants, alors que les registres généraux n'étaient pas renommés. La raison est que quand on un budget en transistor limité, on ne peut pas renommer les deux. Et autant c'est utile de renommer les registres généraux, autant la pile de registre du x87 rendait cela quasiment nécessaire pour avoir des performances convenables.
un autre détail est que les optimisations du renommages sont d'abord arrivées sur le pipeline flottant, avant d'être portées sur le pipeline entier. Un exemple est celui des techniques d'élimination des MOV (''MOV elimination''), qui traitent les instructions MOV entre registres directement dans l'unité de renommage. Ou encore, les techniques avec des idiomes pour mettre à zéro un registre en le Xorant avec lui-même. Elles ont été utilisées d'abord sur les registres flottants/SIMD, avant d'arriver sur les registres généraux. Quelques processeurs les utilsaient pour les registres flottants, mais pas les registres généraux.
Il en est de même avec le renommage dans le ROB versus à banc de registre physique. Quelques CPU renommaient les registres flottants avec un renommage à banc de registre physique, avec que le pipeline entier utilisait encore le renommage dans le ROB. Les processeurs Athlon et Phenom d'AMD étaient dans ce cas, par exemple. Chez Intel, le renommage à banc de registre physique est apparu sur le Pentium 4 et a été appliqué sur tous les registres.
En général, la FPU est précédée par une station de réservation unique (ou par une fenêtre d'instruction sur certains CPU). Il y a souvent un port d'émission pour l'additionneur flottant et un autre pour le multiplieurs, sauf pour les tout premiers modèles d'Intel et AMD. Il y aussi souvent un port pour les instructions FSTORE, les écritures flottantes. Les ports en question servent à transmettre les données à écrire vers l'unité mémoire, ainsi que l'adresse mémoire associée.
{|class="wikitable"
|+ Station de réservation flottante
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3
|-
| FADD (additionneur flottant) || FMUL (multiplication flottante || FSTORE
|}
===Un aparté sur les instructions SIMD des processeurs x86===
L'extension flottante x87 est resté la norme pendant une bonne décennie, sur les CPU 32 bits. Mais par la suite, les choses ont changées avec l'introduction des '''instructions SIMD'''. Je ne vais pas rentrer dans le détail, un chapitre ultérieur détaillera les instructions SIMD. Mais je dois cependant parler des bases de ces instructions, car elles ont remplacé les instructions flottantes progressivement.
Mais avant toute chose, un petit historique,pour vous faire comprendre le vocabulaire utilisé dans ce qui suit. Les premières instructions SIMD ont été introduites en 1996, sur le Pentium MMX. Les instructions en question étaient regroupées dans une '''extension x86''' (un ajout au jeu d'instruction de base), appelé le MMX. Il ne contenait que des instructions SIMD pour des opérandes entiers. Mais par la suite, les extensions '''''Streaming SIMD Extensions''''' (SSE) ont ajouté des opérations flottantes. Les ''Streaming SIMD Extensions'', qu'on va abrévier SSE dans ce qui suit, ont été déclinées en plusieurs versions : SSE1, SSE2, SEE3, SSE4, et quelques intermédiaires. Puis, vint le '''AVX''' (''Advanced Vector eXtensions'').
Les instructions SIMD regroupent plusieurs opérations identiques en une seule instruction, la seule contrainte étant que les opérandes soient différentes. Par exemple, une instruction d'addition SIMD regroupe quatre additions séparées, chacun travaillant sur des opérandes différentes.
[[File:Instructions SIMD.png|centre|vignette|upright=3|Instructions SIMD]]
J'ai donné l'exemple d'une addition SIMD qui regroupe quatre additions flottantes, travaillant chacune sur ses opérandes rien qu'à elle. Les opérandes en question ne sont pas lues depuis les registres généraux ou flottants. A la place, les instructions utilisent un banc de registre dédié, qui contient des registres assez larges. Les instructions SIMD utilisent donc des '''registres SIMD''', qui mémorisent des paquets d'opérandes.
Par exemple, l'extension SSE a ajouté 16 registres XMM, de 128 bits chacun. un registre de 128 bits peut mémoriser : 2 entiers/flottants 64 bits, 4 entiers/flottants 32 bits, 8 entiers 16 bits, 16 entiers 8 bits.
[[File:Vector register.png|centre|vignette|upright=2|Contenu d'un vecteur en fonction du type de données utilisé.]]
L'AVX, quant à elle, donne accès à 16 registres de 256 bits chacun. Pour faire des économies de circuits, ils ont été fusionnés avec les registres XMM du SSE. Les 128 bits de poids faible de ces registres ne sont autre que les registres du SSE.
[[File:AVX registers.svg|centre|vignette|upright=1.5|Registres AVX.]]
Si je parle de ces instructions et registres SIMD, c'est parce qu'ils ont remplacé les instructions/registres flottants d'antan. Le x87 a disparu, les instructions SIMD font office d'instructions flottantes. Le remplacement a eu lieu avec le passage au 64 bits. Les raisons à cela sont multiples, mais la principale est que le x87 était très mal fait, avec une architecture obsolète. Une annexe en parle en détail, mais le x87 utilisait une pile de registre totalement inhabituelle, que les compilateurs ne pouvaient pas utiliser convenablement. Aussi, lors du passage au 64 bits, Intel et AMD en ont profité pour se débarrasser du x87 et le remplacer par quelque chose de plus moderne. Vu que les extensions SIMD faisaient déjà des calculs flottants, elles l'ont remplacé.
Et même dans le processeur, dans sa microarchitecture, le remplacement a été total. Le processeur intègre une ou plusieurs unités de calcul spécialisées dans les instructions SIMD. Et elles remplacent la FPU, totalement. Typiquement, il y a au minimum :
* une unité SIMD spécialisée dans les additions entières ;
* une unité SIMD spécialisée dans les multiplications entières ;
* une unité SIMD spécialisée dans les additions flottantes ;
* une unité SIMD spécialisée dans les multiplications flottantes.
* éventuellement une unité SIMD annexe pour les autres instructions SIMD, et elles sont nombreuses.
Les unités SIMD étaient parfois mutualisées avec la FPU, c'est à dire que la FPU x87 était en réalité une sous-partie de l'unité SIMD. Après tout, l'unité SIMD contient plusieurs additionneurs flottants 32/64 bits, idem pour les multiplieurs flottants. Alors autant en profiter pour leur faire faire des calculs flottants normaux, ceux de la FPU x87.
==Les processeurs x86 d'Intel==
Nous allons voir les processeurs Intel à part des processeurs AMD. La raison à cela est que les architectures Intel et AMD ont progressivement évolué, chacune se basant sur la précédente et l'améliorant. Il n'y a pas eu de cassure entre microarchitectures AMD, qui sont chacune la suite de la précédente. Il est donc préférable de voir les architectures AMD dans l'ordre chronologique. Par contre, Intel a eu une gigantesque cassure, avec le processeur Pentium 4. Son architecture se démarquait fortement du Pentium 3, mais elle n'a pas convaincu et a été abandonnée avec les processeurs suivants. Ce qui fait nous verrons l'architecture du Pentium 4 à part.
Nous allons commencer avec les processeurs Intel. N'y voyez pas du favoritisme derrière ce choix, la justification est toute autre. Si je commence par Intel, c'est pour commencer avec les Pentium 1 et 2, qui sont superscalaires, mais sans exécution dans le désordre. L'absence d'exécution dans le désordre les rend bien plus simples à étudier que les autres, ce qui en fait un bon point de départ pour ce chapitre. Les autres processeurs x86 sont tous avec à exécution dans le désordre, à une exception qu'on verra dans le chapitre suivant. AMD n'a pas produit de processeurs ''in-order'', tous les processeurs produits par AMD intègrent l'exécution dans le désordre.
Le Pentium 1 et 2 utilisaient la même architecture, qu'on détaillera dans ce qui suit. Les seules différences importantes étaient la fréquence, le cache, et quelques détails dans le genre. Le Pentium 3 était une nouvelle microarchitecture qui ajoutait l'exécution dans le désordre. Un an et demi plus tard, le Pentium 4 est sorti et a été un échec. Ses performances étaient peu convaincantes face au Pentium 3, et sa consommation énergétique était très importante. La conséquence est que le Pentium 4 et le Pentium 3 ont survécu pendant un long moment, beaucoup de monde préférait acheter un Pentium 3.
Intel a alors amélioré les microarchitectures du Pentium 3 et du 4, indépendamment, pendant environ 7 ans. La microarchitecture du Pentium 3 a subit plusieurs micro-évolutions, chacune avec une finesse de gravure différente, afin de satisfaire les consomateurs. Les premeirs Pentium 3 avaient une finesse de gravure de 250 nm, elle a chuté à 65 sur les derniers modèles.
L'architecture du Pentium 4 a fait la même chose, pour tenter de corriger ses problèmes de performance et de consommation d'énergie. Les premiers Pentium 4 avaient uen finesse de gravure de 180 nm, elle a elle aussi chutée à 65 sur les derniers modèles.
Après l'échec du Pentium 4, les ingénieurs d'Intel ont repris l'architecture P6 et l'ont améliorée fortement, pour donner l'architecture Core. Les micro-processeurs suivants ont fait évoluer cette architecture progressivement, au point où elle ne ressemble plus à l'originale. L'architecture Core a laissé la place à l'architecture Nehalem, puis Sandy Bridge, puis Haswell, puis Skylake, puis Ice Lake, et Golden Cove. Il s'agit de la lignée principale, partant du Pentium 3 et continuant jusqu'à nos jours. Ces microarchitectures ont suivi un motif assez simple, appelé modèle '''tick-tock'''. Chaque microarchitecture était déclinée en deux versions, la seconde ayant une finesse de gravure réduite.
En parallèle, Intel a travaillé sur des processeurs basse performance et basse consommation, avec une microarchitecture très différente. Les processeurs Atom de microarchitecture Bonnel, pour être ensuite remplacés par les microarchitectures Silvermont, puis Goldmont et Gracemont. Ces microarchitectures ont évolué en parallèle de la lignée principale, il s'agit d'une lignée secondaire. Le tout est résumé dans ce schéma ci-dessous.
[[File:IntelProcessorRoadmap-4v.svg|centre|vignette|upright=2.5|Roadmap des processeurs Intel, qui servira de structure pour la suite du chapitre]]
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire.
Ceci étant dit, commençons par le commencement, avec le Pentium 1.
===Le Pentium 1/MMX et les pipelines U/V===
Le processeur Pentium d'Intel avait un pipeline de 5 étages : un étage de chargement/prédiction de branchement, deux étages de décodage, un étage d'exécution et un dernier étage pour l'écriture dans les registres. Le Pentium 1 était un processeur double émission, intégrant deux pipelines nommés U et V. Chose importante, les deux pipelines n'étaient pas identiques. Le pipeline U pouvait exécuter toutes les instructions, mais le pipeline V était beaucoup plus limité. Par exemple, seul le pipeline U peut faire des calculs flottants, le pipeline V ne fait que des calculs entiers et des branchements.
Les deux pipelines disposaient d'une unité de calcul entière, identique dans les deux pipelines. Mais le pipeline U incorporait un circuit multiplieur/diviseur et d'un ''barrel shifter''. L'unité flottante était sur le port d'émission du pipeline U, idem pour l'unité de calcul vectoriel MMX sur le Pentium MMX. Les deux pipelines avaient chacun une unité de calcul d'adresse, mais ils n'étaient pas identiques : celle du pipeline V ne gérait que l’instruction LEA, celle du pipeline U gérait tous les calculs d'adresse.
{|class="wikitable"
|-
! Pipeline U
! Pipeline V
|-
| ALU entière
| ALU entière
|-
| Multiplieur/diviseur
|
|-
| ''Barrel Shifter''
|
|-
| AGU complexe
| AGU simple (opération LEA)
|-
| FPU
|
|-
| Unité SIMD
|
|}
Les deux pipelines géraient les opérations bit à bit, les additions, les soustractions et les comparaisons. Les autres instructions ne sont exécutables que dans le pipeline U. Pour être plus précis, les deux pipelines supportaient les instructions suivantes, ce qui fait qu'on pouvait en faire deux en même temps :
* Les instructions arithmétiques INC, DEC, ADD, SUB ;
* l'instruction de comparaison CMP ;
* les instructions bit à bit AND, OR, XOR ;
* l'instruction de calcul d'adresse LEA ;
* l'instruction MOV (dépend du mode d'adressage) ;
* les instructions de gestion de la pile PUSH et POP (dépend du mode d'adressage) ;
* l'instruction NOP, qui ne fait rien.
Il faut noter qu'il y a cependant quelques restrictions, beaucoup de paires d'instructions sont interdites. La plupart interdisent au pipeline V de faire quoique ce soit quand une opération particulière est émise dans le pipeline U. Par exemple, si le pipeline U exécute une multiplication ou une division, le processeur ne peut pas exécuter une opération dans le pipeline V. Et c'est pareil avec les branchements : si un branchement est émis dans le pipeline U, l'instruction suivant le branchement n'est pas émise dans le pipeline V, pour éliminer les dépendances de contrôle. De même, si le pipeline U exécute une opération flottante, le pipeline V ne pourra rien exécuter. La seule exception est l'instruction FCXH, qui échange deux registres flottants.
[[File:Intel Pentium arch.svg|centre|vignette|upright=2.5|Microarchitecture de l'Intel Pentium MMX. On voit que certaines unités de calcul sont dupliquées.]]
Un choix assez intéressant a été fait pour le cache de données. Nous avions vu dans le chapitre sur les CPU superscalaires que la superscalarité a un impact sur l'unité mémoire. Il y a alors deux implémentations. La première ne fait rien, l'unité mémoire ne change pas, et le processeur ne peut pas faire deux accès mémoire simultanés. La seconde duplique l'unité mémoire et les ports de lecture/écriture du cache, ce qui autorise des accès mémoire simultanés. Le Pentium 1 utilise une solution intermédiaire.
Les ingénieurs d'Intel étaient partis à la base sur un cache totalement double port, pour obtenir des performances maximales. Mais diverses simulations et observations les ont fait changer d'avis. Les simulations ont montré qu'il est "rare" que les deux pipelines aient besoin de lire/écrire dans le cache en même temps. Et ils ont optimisé le cache de donnée pour en tenir compte. Le cache de données est partiellement multiport : simple port sur certains aspects, double port sur d'autres.
Le cache est un cache splité, à savoir que les données et les ''tags'' sont séparés dans des mémoires séparées. La mémoire pour les ''tags'' est multiport, ce qui permet d'interroger les ''tags'' du cache deux fois par cycle. Un port est relié au pipeline u, un autre au pipeline V. Mais pour les données, le cache n'a qu'un seul port pour lire/écrire des données. Impossible donc de lire deux données en même temps, pour alimenter les deux pipelines. Il utilise cependant 8 banques permet d'accélérer des accès mémoire proches dans le temps, mais dans des cycles d'horloge différents.
Entre les deux mémoires, il y a un circuit qui détecte les conflits, à savoir les situations où les deux pipelines accèdent en même temps au cache. S'ils veulent lire/écrire une donnée dans le même cycle, ce qui est impossible avec un seul port, le pipeline U a la priorité et le pipeline V attend le cycle suivant. Le circuit détecte aussi les conflits de banque, à savoir quand un pipeline accède à une banque en cours d'accès par l'autre pipeline (on rappelle qu'un accès au cache prend plusieurs cycles). Le circuit détecte aussi certaines dépendances mémoires, à savoir des accès consécutifs à la même adresse.
: La TLB du processeur est aussi totalement double port.
===La microarchitecture P6 du Pentium 2/3===
Le Pentium 3 utilisait la '''microarchitecture P6''', qui a été dérivée dans de nombreuses variantes, dont les finesses de gravure n'étaient pas les mêmes. Il introduit une exécution dans le désordre simple, avec une fenêtre d'instruction centralisée, avec renommage dans le désordre dans le ROB (tampon de ré-ordonnancement), commandé par une table d'alias. C'était un processeur triple émission, soit une instruction de plus que la double émission du Pentium 1.
Le pipeline passe de 5 étage sur le Pentium à 14 - 12 étages, dont le détail est le suivant :
* Prédiction de branchement, deux cycles ;
* Chargement des instructions, trois cycles ;
* Décodage de l'instruction, deux cycles ;
* Renommage de registre, un cycle ;
* Copie des opérandes dans le tampon de ré-ordonnancement (lié au renommage de registre dans le ROB) ;
* Dispath dans ou depuis la station de réservation.
* Exécution de l'instruction ;
* Écriture du résultat dans le ROB ;
* Écriture dans le banc de registre physique.
Les instructions sont chargées par blocs de 16 octets, avec un système de fusion de blocs pour gérer les instructions à cheval sur deux blocs. Lors d'un branchement, deux blocs doivent être chargés si l'instruction de destination n'est pas alignée sur 16 octets et cela cause un délai de un cycle d'horloge.
Le décodage des instructions x86 était géré par plusieurs décodeurs. Il y avait trois décodeurs : deux décodeurs simples, et un décodeur complexe. Les décodeurs simples décodaient les instructions les plus fréquentes, mais aussi les plus simples, qui étaient décodées en une seule micro-opération. Les instructions CISC complexes étaient gérées uniquement par le décodeur complexe, basé sur un microcode, qui pouvait fournir jusqu'à 4 micro-opérations par cycle. Le tout est résumé avec la règle 4-1-1. La toute première instruction chargée depuis la file d'instruction va dans le premier décodeur simple. Si jamais le décodeur ne peut pas décoder l'instruction, l'instruction est redirigée dans un autre décodeur, avec un délai d'un cycle d'horloge.
Les stations de réservations étaient regroupées dans une structure centralisée, en sortie de l'unité de renommage. Elles avaient 5 ports d'émission, qui étaient sous-utilisés en pratique. Niveau ALU, on trouve deux ALUs entières, une flottante, une unité pour les instructions SSE et autres, et trois unités pour les accès mémoire (regroupées en une seule unité dans le schéma ci-dessous). Les unités mémoire regroupent une unité de calcul d'adresse pour les lectures, une autre pour les écritures, et une unité pour la gestion des données à écrire. Les unités de calcul d'adresse sont des additionneurs à 4 opérandes, complétement différents des ALU entières. Les ALU entières sont deux unités asymétriques : une ALU simple, et une ALU complexe incorporant un multiplieur. Les deux peuvent exécuter des opérations d'addition, soustraction, comparaison, etc.
[[File:P6 func diag.png|centre|vignette|upright=2|P6 func diag]]
Les premiers Pentium 3 n'avaient pas de cache L2 dans le processeur, celui-ci était sur la carte mère. Mais il a été intégré dans le processeur sur la seconde version du Pentium 3, la version Coppermine.
Le Pentium 3 a servi de base aux microarchitectures d'Intel qui ont suivi. Les changements à chaque nouvelle génération sont assez mineurs : la prédiction de branchement est améliorée, la taille des stations de réservation et du ROB augmente, idem avec les autres structures liées à l'exécution dans le désordre. Les processeurs Intel ont conservé une fenêtre d'instruction centralisée, alors qu'AMD utilise une autre méthode, comme nous allons le voir dans ce qui suit. Les seuls changements notables sont est le passage à un renommage dans le ROB à un renommage à banc de registre physique, ainsi que l'introduction du cache de micro-opération. Et ce sont des modifications qu'AMD a aussi faites, celle-ci étant clairement une bonne idée pour toutes les micro-architectures avec un budget en transistor suffisant. Il est intéressant de garder cela en tête, car une bonne partie des améliorations de chaque micro-architecture proviendra de là.
===La microarchitecture Core===
La '''microarchitecture Core''' fait suite au Pentium 4, mais reprend en fait beaucoup d’éléments du Pentium 2 et 3. Elle utilise la station de réservation unique avec renommage dans le ROB, provenant du Pentium 2/3. Elle supporte aussi les optimisations des opérations ''load-up'', avec notamment un support des macro-opérations mentionnées plus haut. Les améliorations sont assez diverses, mais aussi assez mineures.
* Le processeur incorpore un cache L2, en plus des caches L1 déjà présents auparavant.
* La prédiction de branchement a été améliorée avec notamment l'ajout d'une ''Fetch Input Queue''.
* L'architecture Core passe à la quadruple émission, soit une instruction de plus que sur le Pentium 2 et 3. Pour cela, un quatrième décodeur est ajouté, il s'agit d'un décodeur simple qui ne fournit qu'une seule micro-opération en sortie.
* Un ''stack engine'' et un ''Loop Stream Detector'' ont été ajoutés, ainsi que le support de la macro-fusion qui fusionne une instruction de test et le branchement qui suit en une seule micro-opération.
* Les techniques de désambiguïsation mémoire sont implémentées sur cette micro-architecture.
Il y a quelques modifications au niveau de l'unité de chargement. La file d'instruction a toujours ce système de fusion de blocs, sauf que les branchements ne causent plus de délai d'un cycle lors du chargement. La file d'instruction est suivie par un circuit de prédécodage qui détermine la taille des instructions et leurs frontières, avant de mémoriser le tout dans une file de 40 instructions.
La station de réservation dispose de 6 ports d'émission, mais on devrait plutôt dire 5. Sur les 5, il y en a un pour les lectures, un pour les écritures. Les deux sont reliées à une ''Load/Store Queue'', appelée ''Memory Ordering Buffer''. Elle est elle-même reliée au cache de données par deux ports : un port de lecture et un port d'écriture. Les trois ports d'émission restants sont connectés aux unités de calcul.
Les trois ports restants sont chacun reliés à une ALU entière et une unité SIMD pour l'extension SSE. Le multiplieur entier est relié au second port d'émission. L'additionneur flottant SIMD/SSE est connecté au port 1, le multiplieur/diviseur flottant SIMD/SSE est connecté au port 0, et l'unité SIMD annexe est connectée sur le port restant. Le résultat que le processeur peut émettre un mix d'opérations flottantes et entière assez varié, avec un mix allant de trois opérations entières à trois opérations flottantes, avec tous les intermédiaires. Le fait de mettre les ALU SIMD sur des ports différents permet d'émettre une addition et une multiplication flottante/SIMD simultanément.
[[Image:Intel Core2 arch.svg|centre|vignette|upright=2|Intel Core microarchitecture]]
===Les microarchitectures Sandy Bridge and Ivy Bridge===
Les micro-architectures suivant la micro-architecture Core ont introduit quelques grandes modifications : le passage à un renommage à banc de registre physique, l'ajout d'un cache de micro-opérations (et d'un ''Loop Stream Detector'').
L'ajout du cache de micro-opérations est un gros changement, particulièrement avec le jeu d’instruction x86. Le décodage des instructions est lent, couteux en énergie. Mais avec l'introduction du cache de micro-opération, la majorité des micro-opérations est non pas décodée, mais lue depuis le cache de micro-opérations. Les décodeurs décodent les instructions pas encore exécutées, mais les exécutions suivantes sont lues depuis le cache de micro-opérations. Et vu la grande présence de boucles, le cache de micro-opérations est l'alimentation principale du pipeline.
Les décodeurs servent surtout à alimenter le cache de micro-opérations, parfois décoder quelques instructions isolées exécutées de-dehors de boucles, pas plus. Concrètement, ils servent pour 10 à 20% des micro-opérations exécutées. Intel a d'ailleurs reflété ce fait dans sa terminologie. Intel distingue deux voies de chargement : le ''legacy pipeline'' et le cache de micro-opérations. L'unité de chargement et les décodeurs sont regroupés dans la voie du ''legacy pipeline''.
Le cache de micro-opérations est complété avec un ''Loop Stream Detector'', placé après le cache en question. Les décodeurs et le cache de micro-opérations alimentent une file de micro-opérations, située juste avant l'étage de renommage de registres. La file de micro-opérations sert en quelque sorte de tampon entre l'étage de "décodage" et celui de renommage. Le ''Loop Stream Detector'' utilise cette file de micro-opérations comme d'un cache lorsqu'une boucle est détectée. Les micro-opérations de la boucle sont lue depuis la file de micro-opérations, pour être envoyée au renommeur de registres.
L'avantage est que le cache de micro-opérations et/ou les décodeurs sont mis en pause et clock-gatés lorsqu'une boucle s'exécute, ce qui réduit la consommation du processeur. Le ''Loop Stream Detector'' et le cache de micro-opération ont globalement le même effet : désactiver tout ce qui est avant, le ''Loop Stream Detector'' appliquant cette méthode au cache de micro-opération lui-même..
Voyons maintenant quelles sont les micro-architectures qui implémentent ces optimisations.
Les microarchitectures '''Sandy Bridge''' and '''Ivy Bridge''' sont similaires à l'architecture Core, si ce n'est pour le passage à un renommage à banc de registre physique, et l'ajout d'un cache de micro-opérations. Le nombre de ports d'émission passe à 7, avec 4 pour les instructions arithmétiques (flottantes comme entière), 2 pour les lectures, et un pour les écritures (en fait deux, avec un pour le calcul d'adresse, l'autre pour la donnée à écrire). Pour le reste, rien ne change si ce n'est la prédiction de branchement
Les architectures '''Haswell''' et '''Broadwell''' ont ajouté quelques unités de calcul, élargit la sortie du cache de micro-opérations. Un port d'émission pour opération entières a été ajouté, de même qu'un port pour les accès mémoire. Le processeur passe donc à 8 ports d'émission, ce qui permet d'émettre jusqu'à 8 micro-opérations, à condition que le cache de micro-opération suive. Pour le reste, le processeur est similaire aux architectures précédentes, si ce n'est que certaines structures grossissent.
L'architecture '''Skylake''' réorganise les unités de calcul et les ports d'émission pour gagner en efficacité. Pour le reste, les améliorations sont mineures. A la rigueur, l'unité de renommage de registre ajoute des optimisations comme l'élimination des MOV, les idiomes liés aux opérations avec zéro, etc. La microarchitecture ''Skylake'' est une microarchitecture capable de décoder/renommer 4 instructions par cycle, elle peut émettre 6 µops par cycle grâce au cache de µops. Elle a été déclinée en plusieurs microarchitectures légérement différentes les unes des autres, nommés '''''Kaby Lake''''', '''''Coffee Lake''''', '''''Comet Lake'''''.
===Les microarchitectures "Cove" d'Intel===
Après Skylake, les microarchitecture d'Intel ont changé de dénomination. Elles sont maintenant nommées non pas avec des noms de lacs, mais ont un nom qui finit par Cove : ''Sunny Cove'', ''Golden Cove'', etc. La seule exception à cette règle est la microarchitecture '''''Palm Cove''''' était un ''die shrink'' de Skylake, ce qui fait qu'elle est à part de celles qui vont suivre, qui sont vraiment des microarchitectures différentes.
La microarchitecture qui suit immédiatement skylake était la microarchitecture '''''Sunny Cove'''''. Elle était au départ prévue pour les PC ''desktop'', mais sa production a eu de nombreux problèmes. Elle était censée être produite avec une technologie de gravure 10nm d'Intel lui-même, qui a cependant eu de nombreux problèmes de production. Au final, ''Sunny Cove'' et ses variantes n'ont existées que dans des CPU mobile, basse performance et basse consommation. Elle a été déclinée en deux versions : '''''Willow Cove''''' a un cache retravaillé, alors que '''''Cypress Cove''''' est l'équivalent de ''Sunny Cove'' mais repassé à une technologie de gravure de 14nm. Les deux microarchitecture ont été utilisées sur les plateformes dites '''''Ice Lake''''' et '''''Tiger Lake'''''.
La microarchitecture ''Sunny Cove'' passe de quadruple émission à la pentuple émission. Précisément, l'unité de renommage de registres est maintenant capable de renommer 5 µops par cycle. De plus, l'unité de renommage devient plus performante. Ses capacités de ''MOV elimination'' ont été améliorées, et elle devient capable de reconnaitre certains idiomes, comme celui qui met à zéro un registre en le XORant avec lui-même.
La microarchitecture '''Golden Cove''' altère les décodeurs et l'unité de chargement. Sur toutes les générations précédentes, on reste sur une unité de chargement qui charge 16 octets à la fois et il y a toujours 4 décodeurs identiques aux générations précédentes. Golden Cove passe à 6 décodeurs simples, et double la taille du chargement qui passe à 32 octets. Une telle stagnation sur les unités de chargement et de décodage s'explique encore une fois par la présence du cache de micro-opération fait que ce n'est pas trop un problème. Tout ce qui précède le cache de micro-opérations n'a pas de raison d'évoluer, car ce cache est très puissant.
Niveau unités de calcul, le CPU a pas moins de 5 ALU entières, deux ''barrel shifters'', un multiplieur et deux unités de branchements. Le tout est répartit sur 5 ports d'émission. Pour les unités mémoire, il y a trois unités LOAD pour les lectures et deux unités STORE pour les écritures. Il n'y a plus d'unité flottante proprement dite, mais une unité SIMD qui est capable de faire plusieurs calculs flottants, qu'on ne détaillera pas ici, car nous n'avons pas encore vu les techniques de SIMD.
Notons qu'il y a trois fenêtres d'instruction séparées pour : les instructions entières/flottantes, les lectures, les écritures. Le processeur peut décoder 6 instructions par cycle, et en émettre 6 à destination des fenêtres d'instructions. Les fenêtres d'instructions peuvent émettre 5 instructions entières, trois lectures et deux écritures en même temps. Le ROB accepte de ''commit'' 8 µops par cycle.
[[File:Golden Cove.png|centre|vignette|upright=3|Golden Cove]]
Il s'agit donc de processeurs superscalaires très larges, ce qui est la norme de nos jours et le restera pendant longtemps.
==Les processeurs x86 d'AMD==
Les architectures Intel ont évolué progressivement, sans grandes cassure. Il y a une continuité presque ininterrompue entre l'architecture du Pentium 2 et les architectures modernes. Intel a fait des améliorations mineures à chaque nouvelle micro-architecture, si on omet le passage à un renommage à banc de registre physique et l'ajout du cache de micro-opération. A l'opposé, les architectures AMD ont eu de nombreuses cassures dans la continuité où AMD a revu sa copie de fond en comble.
Une différence entre les microarchitectures AMD et Intel est qu'Intel préfére utiliser une fenêtre d'instruction centralisée, alors qu'AMD préfére des stations des fenêtres d'instruction décentralisées. Une autre différence est que les unités flottantes/SIMD sont bien séparées des unités entières, elles ont des ports bien séparés. Intel, en comparaison, préfére mettre une ALU et une unité FPU/SIMD sur le même port d'émission. Mais il s'agit là de tendance, avec de nombreuses exceptions. Par exemple, les microarchitectures K6 et Bulldozer d'AMD utilisaient une station de réservation centralisée.
Étudier ces architectures demande de voir trois choses séparément : le ''front-end'' qui regroupe l'unité de chargement et les décodeurs, le ''back-end'' qui gère l'exécution dans le désordre et les unités de calcul, et le sous-système mémoire avec les caches et la ''Load Store Queue''. Leur étude sera plus ou moins séparée dans ce qui suit, pour chaque classe d'architecture.
===La première génération de CPU AMD : les architectures K5, K6, K7, K8 et K10===
La première génération de processeurs AMD est celle des architectures K5, K6, K7, K8 et K10. Il n'y a pas de K9, qui a été abandonné en cours de développement. Les processeurs K5 et K6 portent ce nom au niveau commercial. Par contre, les processeurs d'architecture K7 sont aussi connus sous le nom d''''AMD Athlon''', les AMD K8 sont connus sous le nom d''''AMD Athlon 64''', et les architecture K10 sont appelées les '''AMD Phenom'''. Comme le nom l'indique, l'architecture K8 a introduit le 64 bits chez les processeurs AMD.
Elles ont une architecture assez similaire pour ce qui est du chargement et des caches. Toutes disposent d'au minimum un cache L1 d'instruction et d'un cache L1 de données. Le K5 n'avait que ces caches, mais un cache L2 a été ajouté avec le K7, puis un L3 avec le K10. L'AMD K5 avait une TLB unique, mais les processeurs suivants avaient une TLB pour le L1 d'instruction et une autre pour le L1 de données. Idem pour le cache L2, avec deux TLB : une pour les données, une pour les instructions.
Les caches L1/L2 sont de type exclusifs, à savoir que les données dans le L1 ne sont pas recopiées dans le L2. Le cache L2 est précisément un cache de victime, qui mémorise les données/instructions, évincées des caches L1 lors du remplacement des lignes de cache. L'introduction du cache L2 a entrainé l'ajout de deux TLB de second niveau : une L2 TLB pour les données et une autre pour les instructions. Les architectures K8 et K10 ont ajouté un cache L3, avec un accès indirect à travers l'interface avec le bus.
: L'AMD K7 originel, aussi appelée Athlon classique, n'avait pas de cache L2, mais celui-ci était placé sur la carte mère et fonctionnait à une fréquence moitié moindre de celle du CPU. L'Athlon Thunderbird, puis l'Athlon XP, ont intégré le cache L2 dans le processeur.
{|class="wikitable"
|-
! Architecture AMD
! colspan="5" | Caches
|-
| rowspan="2" | K5
| L1 instruction || L1 données || colspan="3" |
|-
| colspan="2" | TLB unique || colspan="3" |
|-
| colspan="4" |
|-
| rowspan="2" | K6
| L1 instruction || L1 données || colspan="3" | L2 unifié
|-
| TLB L1 instruction || TLB L1 données || colspan="3" |
|-
| colspan="6" |
|-
| rowspan="2" | K7, K8
| L1 instruction || L1 données || colspan="2" | L2 unifié ||
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|-
| colspan="6" |
|-
| rowspan="2" | K10
| L1 instruction || L1 données || colspan="2" | L2 unifié || L3
|-
| TLB L1 instruction || TLB L1 données || TLB L2 instruction || TLB L2 données ||
|}
Fait important, les architectures K5 à K10 utilisent la technique du '''prédécodage''', où les instructions sont partiellement décodées avant d'entrer dans le cache d'instruction. Le prédécodage facilite grandement le travail des décodeurs d'instruction proprement dit. Par contre, le prédécodage prend de la place dans le cache L1 d'instruction, une partie de sa capacité est utilisé pour mémoriser les informations prédécodées. C'est donc un compromis entre taille du cache et taille/rapidité des décodeurs d'instruction.
Sur les architectures K5 et K6, le prédécodage précise, pour chaque octet, si c'est le début ou la fin d'une instruction, si c'est un octet d'opcode, en combien de micro-opérations sera décodée l'instruction, etc. A partir de l'AMD K7, le prédécodage reconnait les branchements inconditionnels. Lorsqu'un branchement inconditionnel est pré-décodé, le pré-décodage tient compte du branchement et continue le pré-décodage des instructions à partir de la destination du branchement. Le système de prédécodage est abandonnée à partir de l'architecture Bulldozer, qui suit l'architecture K10.
La prédiction de branchement de ces CPU tire partie de ce système de pré-décodage, à savoir que les prédictions de branchement sont partiellement mémorisées dans les lignes de cache du L1 d'instruction. Par exemple, l'AMD K5 se passe de ''Branch Target Buffer'' grâce à cela. Si une ligne de cache contient un branchement, elle mémorise l'adresse de destination de ce branchement, en plus des bits de pré-décodage. Si il y a plusieurs branchements dans une ligne de cache, c'est l'adresse de destination du premier branchement pris dans cette ligne de cache qui est mémorisée.
Un défaut de cette approche est que si le branchement n'est pas dans le L1 d'instruction, aucune prédiction de branchement ne peut être faite et le préchargement ne peut pas fonctionner. C'est une limitation que n'ont pas les BTB découplées du cache L1 : elles peuvent prédire un branchement qui a été évincé dans le L2 ou le L3, tant que l'entrée associée est dans le BTB. Les prédictions peuvent même servir à précharger les instructions utiles.
[[File:Comparaison du chargement de l'AMD K5 et K6.png|centre|vignette|upright=2|Comparaison du chargement de l'AMD K5 et K6]]
Au niveau du décodage, on trouve de nombreuses différences entre les premières architectures AMD. L'AMD K5 contient 4 décodeurs hybrides, afin de décoder 4 instructions par cycles. Le K5 a quatre décodeurs simples couplés à 4 décodeurs complexes avec chacun un accès au micro-code. Une instruction peut donc passer par a donc deux voies de décodage : un décodage rapide et simple pour les instructions simples, un décodage lent et passant par le microcode pour les instructions complexes. Pour décoder 4 instructions, les deux voies sont dupliquées en 4 exemplaires, ce qui a un cout en circuits non-négligeable.
L'AMD K6 utilise moins de décodeurs et ne peut que décoder deux instructions à la fois maximum. Par contre, il fournit en sortie 4 micro-opérations. Il intègre pour cela deux décodeurs simples, un décodeur complexe et un décodeur micro-codé. Un décodeur simple transforme une instruction simple en une ou deux micro-opérations. Il est possible d'utiliser les deux décodeurs simples en même temps, afin de fournir 4 micro-opérations en sortie du décodeur. Les deux autres décodent une instruction complexe en 1 à 4 micro-opérations. Si jamais la ou les deux instructions sont décodées en 1, 2 ou 3 micro-opérations, les micro-opérations manquantes pour atteindre 4 sont remplies par des NOPs.
Pour le K7 et au-delà, le processeur dispose de décodeurs séparées pour les instructions micro-codées de celles qui ne le sont pas. Le processeur peut décoder jusqu’à 3 instructions par cycle. Le décodage d'une instruction microcodée ne peut pas se faire en parallèle du décodage non-microcodé. C'est soit le décodeur microcodé qui est utilisé, soit les décodeurs câblés, pas les deux en même temps. Le décodage d'une instruction prend 4 cycles. Les instructions non-microcodées sont décodées en une seule micro-opération, à un détail près : le CPU optimise la prise en charge des instructions ''load-up''.
La différence entre le K6 et le K7 s'explique par des optimisations des instructions ''load-up''. Sur le K6, les instructions ''load-up'' sont décodées en deux micro-opération : la lecture en RAM, l'opération proprement dite. Mais sur le K7, une instruction ''load-up'' est décodée en une seule micro-opération. En conséquence, les décodeurs simples sont fortement simplifiés et le décodeur complexe disparait au profit d'un microcode unique.
[[File:Décodage sur le K5 et le K5.png|centre|vignette|upright=3|Décodage sur le K5 et le K5]]
====Les micro-architectures K5 et K6 d'AMD====
Les deux premières architectures étaient les architectures K5 et K6, l'architecture K6 ayant été déclinée en quatre versions, nommées K6-1, K6-2, et K-3, avec une version K6-3 bis. Elles sont regroupées ensemble car elles ont beaucoup de points communs. Par exemple, tout ce qui a trait au chargement et au cache était similaire, de même que les unités de calcul.
Les deux architectures avaient n'avaient pas de cache L2 et devaient se contenter d'un cache L1 d'instruction et d'un cache L1 de données. L'AMD K5 incorpore une TLB unique, alors que le K6 utilise des TLB séparées pour le cache d'instruction et le cache de données. Une différence entre l'architecture K5 et K6 est que la première utilise des caches normaux, alors que la seconde utilise des ''sector caches''.
Les deux architectures disposaient des unités de calcul suivantes : deux ALU entières, une FPU, deux unités LOAD/STORE pour les accès mémoire, une unité de branchement et une ou plusieurs unités SIMD. Une organisation classique, donc.
Pour les unités entières, il y avait deux ALU simples, un ''barrel shifter'' et un diviseur. Il n'y a pas d'erreur, le processeur incorpore un circuit diviseur, mais pas de circuit multiplieur. La raison est que la multiplication est réalisée par la FPU ! En effet, le multiplieur flottant de la FPU intègre un multiplieur entier pour multiplier les mantisses, qui est utilisé pour les multiplications entières. La même technique a été utilisée sur l'Atom, comme vu plus haut. Le tout était alimenté par deux ports d'émission, appelés ports X et Y. Sur l'architecture K5, le ''barrel shifter'' et le diviseur sont des ports différents.
{|class="wikitable"
|+ AMD K5
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
| ''Barrel Shifter''
| Diviseur
|}
Sur l'architecture K6, le ''barrel shifter'' et le diviseur sont sur le même port.
{|class="wikitable"
|+ AMD K6
|-
! Port X
! Port Y
|-
| ALU simple
| ALU simple
|-
|
| ''Barrel Shifter''
|-
|
| Diviseur
|}
Niveau unités mémoire, le K5 avait deux unités LOAD/STORE, chacune capable de faire lecture et écriture. Par contre, la ''store queue'' n'a qu'un seul port d'entrée, ce qui fait que le processeur peut seulement accepter une écriture par cycle. Le processeur peut donc émettre soit deux lectures simultanées, soit une lecture accompagnée d'une écriture. Impossible d'émettre deux écritures simultanées, ce qui est de toute façon très rare. L'architecture K6 utilise quant à elle une unité LOAD pour les lectures et une unité STORE pour les écritures. Ce qui permet de faire une lecture et une écriture par cycle, pas autre chose.
Niveau unités SIMD, l'architecture K7 n'avait qu'une seule unité SIMD, placée sur le port d'émission X. L'architecture K8 ajouta une seconde unité SIMD, sur l'autre port d'émission entier. De plus, trois ALU SIMD ont été ajoutées : un décaleur MMX, une unité 3DNow!, une unité mixte MMX/3DNow. Elles sont reliées aux deux ports d'émission entier X et Y ! Elles ne sont pas représentées ci-dessous, par souci de simplicité.
[[File:Unité de calcul des processeurs AMD K5 et K6.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K5 et K6. les unités sur la même colonnes sont reliées au même port d'émission.]]
Si les unités de calcul et le chargement sont globalement les mêmes, les deux architectures se différencient sur l'exécution dans le désordre. L'AMD K5 utilise du renommage de registre dans le ROB avec des stations de réservation. Par contre, l'AMD K6 utilise une fenêtre d'instruction centralisée. De plus, son renommage de registre se fait avec un banc de registre physique.
L'architecture AMD K5 utilisait de deux stations de réservation par unité de calcul, sauf pour les deux unités mémoire partageaient une station de réservation unique (deux fois plus grande). Les stations de réservation sont cependant mal nommées, vu que ce sont en réalité des mémoire FIFO. Une micro-opération n'est émise que si elle est la plus ancienne dans la FIFO/station de réservation. Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique. Le tampon de ré-ordonnancement faisait seulement 16 instructions.
[[File:AMD K5.jpg|centre|vignette|upright=3|AMDK5 Diagramme.]]
L'architecture K6 remplace les stations de réservations par une fenêtre d'instruction centralisée. Les 4 micro-opérations renommées sont écrites dans la fenêtre d'instruction par groupe de 4, NOP de ''padding'' inclus. La fenêtre d'instruction centralisé contient 24 micro-opérations, groupées en 6 groupes de 4 micro-opérations, avec potentiellement des NOP dedans suivant le résultat du décodage. L'avantage est que l'implémentation de la fenêtre d'instruction est simple. La fenêtre d'instruction centralisée permettait d'émettre 6 micro-opérations en même temps (une par unité de calcul/mémoire). Le renommage de registres se faisait dans le tampon de ré-ordonnancement, il n'y avait pas encore de banc de registre physique.
Le processeur utilisait un renommage avec un banc de registre physique. Le banc de registre physique pour les entiers contenait 48 registres, dont 24 étaient des registres architecturaux et 24 étaient des registres renommés. Sur les 24 registres architecturaux, 16 avaient une fonction de ''scratchpad'' que les ''datasheets'' d'AMD ne détaillent pas, les 8 restants étaient les registres généraux EAX, EBX, etc.
[[File:AMD K6 Little foot & Modl 6.png|centre|vignette|upright=3|AMD K6 original.]]
====Les micro-architectures K7, K8 et K10 d'AMD====
Les micro-architectures suivantes sont les architectures K7, K8 et K10. Les architectures K7, K8 et K10 sont assez similaires. La différence principale entre le K7 et le K8 est le support du 64 bits. Les apports du K10 sont la présence d'un cache L3, d'une unité de calcul supplémentaire et d'améliorations de la prédiction de branchement. La taille de certains caches a été augmentée, de même que la largeur de certaines interconnexions/bus.
L'architecture K7 des processeurs Athlon utilisait le renommage de registre, mais seulement pour les registres flottants, pas pour les registres entiers. Le ranommeg des registres flottants étaient réalisé via un banc de registres physique, ne contenant que des registres flottants. Les architectures K8 et K10 utilisent le renommage de registres pour tous les registres, entiers comme flottants. Par contre, le renommage de registre n'est pas réalisé de la même manière pour les registres entiers et flottants. Les registres entiers sont renommés dans le tampon de ré-ordonnancement, comme c'était le cas sur les architectures Intel avant le Pentium 4. Par contre, les registres flottants sont renommés grâce à un banc de registre physique. Le K8 est donc un processeur au renommage hybride, qui utilise les deux solutions de renommage principales.
A partir du K7, le CPU optimise la prise en charge des instructions ''load-up''. Les instructions ''load-op'' sont appelées des macro-opérations dans la terminologie d'AMD, et aussi d'Intel. L'idée est que les instructions ''load-up'' sont décodées en micro-opérations intermédiaires. Elles sont propagées dans le pipeline comme étant une seule micro-opération, jusqu'à l'étage d'émission. Lors de l'émission, les instructions ''load-up'' sont scindées en deux micro-opérations : la lecture de l'opérande, puis l'opération proprement dite. Faire ainsi économise des ressources et optimise le remplissage du tampon de ré-ordonnancement, des fenêtres d'instructions, des stations de réservation, etc.
Le tampon de réordonnancement est combiné avec divers circuits en charge de l'exécution dans le désordre, dans ce qui s'appelle l'''instruction control unit''. Il contient de 72 à, 84 instructions, qui sont regroupées en groupes de 3. Là encore, comme pour le K5 et le K6, le tampon de réordonnancement tient compte de la sortie des décodeurs. Les décodeurs fournissent toujours trois micro-opérations par cycle, quitte à remplir les vides par des NOP. Le tampon de réordonnancement reçoit les micro-opérations, NOP inclus, par groupes de 3, et est structuré autour de ces triplets de micro-opération, y compris en interne.
Pour ce qui est de l'unité mémoire, elle est précédée par une file de µops mémoire, qui émet les accès mémoire dans l'ordre du programme. Elle est souvent qualifiée de ''Load-Store Queue'', mais ce n'est pas la terminologie que nous utilisons dans ce cours. La file de micro-opération lire/écrire 64 bits par cycle depuis le cache L1, ce qui fait un seul accès au cache par cycle. La file de µops mémoire est appelée la ''Pre-Cache Queue''. Si au vu de son nom, vous avez deviné qu'il y avait une ''Post-Cache Queue''. Elle mémorise les lectures/écritures émises, mais qui ont levé un défaut de cache L1. Elle ne fait pas partie de la file de µops mémoire proprement dite.
Les architectures K7, K8 et K10 ont des unités de calcul très similaires. Concrètement, il y a trois ALU entières, trois unités de calcul d'adresse, et une FPU. Le processeur incorpore, aussi un multiplieur entier, relié sur le port d'émission de la première ALU. La FPU regroupe un additionneur flottant, un multiplieur flottant, et une troisième unité LOAD/STORE pour les lectures/écritures pour les nombres flottants. L'architecture K8 ajoute une unité de manipulation de bit, la K10 un diviseur entier.
[[File:Unité de calcul des processeurs AMD K7, K8 et K10.png|centre|vignette|upright=2|Unité de calcul des processeurs AMD K7, K8 et K10]]
La manière d'alimenter les ALU en micro-opérations varie un petit peu entre les architectures K7, K8 et K10. Il y a cependant quelques constantes entre les trois. La première est qu'il y a une fenêtre d'instruction séparée pour les flottants, de 36 à 42 entrées, avec renommage de registre. La fenêtre d'instruction flottante a trois ports d'émission : un pour l'additionneur flottant, un autre pour le multiplieur, et un troisième pour la troisième unité flottante qui s'occupe du reste. La seconde est que chaque ALU entière est couplée avec une unité de calcul d'adresse. Par contre, la méthode de couplage varie d'un processeur à l'autre.
: Les stations de réservation sont nommées des ''schedulers'' dans les schémas qui suivent.
La micro-architecture K7 avait deux fenêtres d'instruction : une pour les opérations flottantes, une autre pour les instructions entières et les accès mémoire. La fenêtre d'instruction entière était reliée à 3 ALU entières et à 3 AGU. Elle pouvait émettre trois micro-opérations en même temps : trois micro-opérations entières, trois micro-opérations mémoire. Les AGU étaient reliées à la file de µops mémoire mentionnée plus haut, ce qui permet d'émettre trois µops mémoire par cycle. Par contre, la file de µops mémoire ne pouvait exécuter qu'une lecture de 64 bits ou une écriture de 64 bits. En clair, trois micro-opérations mémoire peuvent être émises par cycle, cela entraine trois calculs d'adresse simultanés, mais les trois lectures/écritures sont mises en attente dans la file de µops mémoire. Elles s'exécutent alors l'une après l'autre.
La fenêtre d'instruction entière contenait 5 à 6 groupes de 3 macro-opérations. Vous noterez que j'ai parlé de macro-opérations et pas de micro-opérations, car les instructions ''load-up'' sont considérées comme une seule "micro-opération" dans la fenêtre d'instruction entière. Et cela se marie bien avec une fenêtre d'instruction unique partagée entre pipeline entier et pipeline mémoire. Une macro-opération était scindée en deux micro-opérations : une micro-opération mémoire et une micro-opération entière. Il est donc avantageux de regrouper unités mémoire et unités entières à la même fenêtre d'instruction pour ce faire.
[[File:AMD K7.png|centre|vignette|upright=3|AMD K7]]
Sur les architectures K8 et K10, la station de réservation unique de 15 micro-opérations est remplacée par trois stations de réservations, de 8 micro-opérations chacune pour le K8, de 10 pour le K10. Chaque station de réservation entière alimente une unité de calcul entière et une unité de calcul d'adresse. l'unité de calcul d'adresse est reliée à la file de µops mémoire, qui n’exécute toujours qu'un seul accès mémoire par cycle. Le multiplieur est relié à la première station de réservation, sur le même port d'émission que l'ALU.
[[File:AMD Husky microarchitecture.png|centre|vignette|upright=3|AMD Husky micro-architecture]]
La micro-architecture K10 a été déclinée en plusieurs versions, nommées Grayhound, Grayhound+ et Husky, Husky étant une architecture gravée en 32 nm dédiée aux processeurs A-3000. L'architecture Grayhound a plus de cache et un ROB plus grand, la Husky est quand à elle un peu plus différente. Elle n'a pas de cache L3, contrairement aux autres architectures K10, ce qui simplifie fortement son sous-système mémoire. Par contre, les fenêtres d'instructions/stations de réservation et le ROB sont plus grands, pareil pour les files dans l'unité mémoire. Une ALU pour les divisions entières a aussi été ajoutée.
Pour résumer, les architectures K7, K8 et K10 séparent les pipelines entiers et flottants : trois pipelines entiers avec chacun son unité de calcul, et un pipeline flottant avec plusieurs unités de calcul. Les raisons à cela sont assez diverses. Disons que dupliquer des ALU entières simples prend peu de transistors, là où les gros circuits comme le multiplieur ou la FPU ne sont pas dupliqués.
Et cela a un autre avantage : le renommage, ''dispatch'' et l'émission sont plus simples. Les pipelines entiers ont une exécution dans le désordre peu complexe, grâce au grand nombre d'unités de calcul, ce qui fait que le pipeline entier est de seulement 15 cycles au total (chargement et décodage inclus). A l'opposé, la FPU est alimentée par une exécution dans le désordre très complexe, avec banc de registre physique et beaucoup de ressources, mais au prix d'un pipeline flottant plus long de 3 cycles, soit 18 cycles au total.
===La micro-architecture Bulldozer et ses dérivés===
Viennent ensuite les '''micro-architectures Bulldozer''', avec trois révisions ultérieures nommées Piledriver, Steamroller et Excavator. Un détail très important de Bulldozer est que de nombreux circuits sont partagés entre deux cœurs, à un point qui mérite de nombreuses explications. Les processeurs Bulldozer peuvent exécuter deux programmes "en même temps", sur deux cœurs partiellement fusionnés. Cette technique de partage d'unités de calcul entre coeurs s'appelle le '''cluster multithreading''', ou encore les '''architectures à cœurs conjoints''' (''Conjoined Core Architectures'').
Les deux cœurs sont séparés pour ce qui est du chemin de données, du ''back-end''. Ils disposent de deux bancs de registres généraux et les ALU entières sont dupliquées. Toute la logique d'exécution dans le désordre est aussi dupliquée, avec deux ROBs, des stations de réservations séparées, etc. L'unité mémoire est aussi dupliquée, chaque cœur dispose de ses propres ''load queue'' et ''store queue'', ainsi de que son propre cache L1 de données.
En clair, les chemins de données sont totalement séparés, sauf sur deux points : la FPU et les unités SIMD. La FPU est partagée entre deux coeurs, les unités SIMD sont aussi dans ce cas. Le partage de la FPU permet d'éviter de dupliquer trop de circuits et donc d'économiser des transistors. Le problème est que ce partage est source de dépendances structurelles, ce qui peut entraîner des pertes de performances.
Par contre, le ''front-end'' est presque totalement partagé entre deux cœurs. Il n'y a qu'un seul cache d'instruction pour les deux cœurs, la TLB du cache d'instruction est unique, l'unité de prédiction de branchement est partagée entre deux cœurs, les décodeurs d'instruction aussi sont partagés, même la file de µops ! Les seules unités qui sont dupliquées sont la file d'instruction et l'unité de renommage, présentes en deux exemplaires. Concrètement, les deux programmes ont accès au cache à tour de rôle. Un programme a accès au cache à un instant t, mais le cycle suivant pourra être alloué à l'autre programme. Une unité décide de quel programme peut charger ses données, idem pour le décodage.
[[File:AMD Bulldozer microarchitecture.png|centre|vignette|upright=3|Microarchitecture Bulldozer d'AMD.]]
Au-delà de ce partage entre cœurs, Bulldozer a eu un changement assez important : les stations de réservation sont passées de stations de réservation très décentralisées à une station de réservation unique (pour les µops entières/mémoire), d'environ 40 entrées. L’implémentation de la station de réservation a aussi changée. Car 40 entrées, c'est beaucoup pour l'époque. Elle subit quelques simplifications pour la rendre plus rapide et plus économe en transistors.
Les autres processeurs de l'époque utilisaient une station de réservation qui détectait à chaque instant quelle était l'instruction la plus ancienne, qui soit prête pour l’exécution. Mais Bulldozer n'utilisait pas cette technique. A la place, il mémorisait la place de l'instruction la plus ancienne gra^ce à une ''ancestry table''. Et l'instruction la plus ancienne est privilégiée sur toute les autres. Mais si elle n'est pas prête à s'exécuter, la station de réservation choisit une instruction suivant sa position dans la station de réservation (qui reste une mémoire RAM/associative, ne l'oublions pas).
Bulldozer utilise aussi les optimisations du contournement et du banc de registre, vues dans le chapitre sur les CPU superscalaires. Le banc de registres généraux est dupliqué, afin de garder des bancs de registres avec peu de ports de lecture/écriture. Chaque banc de registre généraux a 4 ports de lecture et deux d'écriture, ce qui permet d'alimenter 2 ALU entières. En tout, cela permet d'alimenter 4 ALU entières, avec 8 ports de lecture et 4 d'écriture. Le tout alimente 2 ALU entières, et 2 AGUs/unités mémoire.
Vu que la FPU est partagée entre deux programmes, sa station de réservation a une taille doublée comparé à la normale. Jugez plutôt : 60 entrées pour la station de réservation, là où les microarchitectures concurrentes d'Intel (''Sandy bridge'') utilisait une station de réservation de 54 entrées pour toutes les instructions entières/flottante/mémoire ! Idem pour le banc de registres flottants, qui n'a pas moins de 160 registres flottants !
Pour ce qui est des instructions SIMD, Bulldozer supporte l'extension AVX, qui fait des calculs du des registres de 256 bits, découpés en entiers/flottants de 64 bits, 32 bits, 16 bits, 8 bits, etc. L'unité SIMD ne gére cependant que des calculs sur 128 bits. Les instructions AVX sont donc découpées en deux µops, chacune traitant la moitié d'un registre SIMD. La performance n'est donc pas améliorée par rapport au SSE. Par contre, les unités SIMD gérent nativement l'opération FMA (une multiplication suivie d'une addition).
{|class="wikitable"
|-
|+ Bulldozer
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4
|-
| FMA - 128 bits || FMA - 128 bits || ALU - 128 bits || ALU - 128 bits
|-
| FMA opérandes entiers - 128 bits || Suffle et permutations || || FSTORE (x87)
|}
La révision Steamroller sépara le ''front-end'' en deux voies distinctes, une par ''thread''. Concrètement, le processeur se contente maintenant de partager la FPU et les unités SIMD entre deux coeurs, pas plus. Pour cela, elle ajouta un second décodeur d'instruction, une seconde file de micro-opération et une seconde unité de renommage de registres, afin d'améliorer les performances.
Niveaux optimisations mineures, les stations de réservation ont été augmentées, elles peuvent mémoriser plus de micro-opérations, idem pour les bancs de registre et les files de lecture/écriture. Un cache de micro-opérations a été ajouté, de même que des optimisations quant au renommage de registre. Des ALU ont aussi été ajoutées, des FPU retirées.
[[File:AMD excavator microarchitecture.png|centre|vignette|upright=3|Microarchitecture Excavator d'AMD.]]
===Les micro-architectures ZEN d'AMD===
Les micro-architectures suivantes sont les '''architectures ZEN 1/2/3/4/5'''. Elles se ressemblent beaucoup, chacune accumulant les améliorations des précédentes. Mais le cœur de l'architecture reste plus ou moins le même. En passant à la suivante, le nombre de registre virtuel augmente, le ''branch target buffer'' augmente en taille, le ROB et les files d'attente grossissent, les caches de micro-opération aussi, les caches grossissent, etc. Une optimisation intéressante est l'ajout d'un cache de micro-opération, qui améliore grandement les performances du ''front-end'', notamment pour les boucles.
La micro-architecture Zen 1 est illustrée ci-dessous. Comme on le voit, les registres flottants ont une unité de renommage séparée de celle pour les entiers, mais les deux utilisent du renommage à banc de registre physique. Il y a par contre une différence au niveau des fenêtres d'instruction, notées ''scheduler'' dans le schéma.
Pour ce qui est des unités de calcul flottantes, il y a une fenêtre unifiée qui alimente quatre ALU, grâce à 4 ports d'émission. Mais pour les ALU entières, il y a une fenêtre d'instruction par ALU, avec un seul port d'émission connecté à une seule ALU. La raison de ce choix est que les opérations flottantes ont un nombre de cycle plus élevé, sans compter que les codes flottants mélangent bien additions et multiplication.
Une fois décodées, les instructions sont placées dans une première file de micro-opérations om elles attendent, puis sont dispatchées soit dans le pipeline entier, soit dans le pipeline flottant. les micro-opérations entières sont insérées dans une fenêtre d'instruction directement, alors que les micro-opérations flottantes doivent patienter dans une seconde file de micro-opérations.
La raison est que les micro-opérations flottantes ayant une grande latence, trop d'instructions flottantes consécutives pourraient bloquer le pipeline flottant, sa fenêtre d'instruction étant pleine. Le pipeline flottant étant bloqué, la première file de micro-opérations serait bloquée et on ne pourrait plus émettre de micro-opérations entières. Pour éviter cela, une solution serait d'agrandir la file de micro-opérations, mais cela la rendrait plus lente et se ferait donc au détriment de la fréquence d'horloge. Alors une solution a été d'ajouter une seconde file de micro-opérations, au lieu d'agrandir la première.
[[File:Zen microarchitecture.svg|centre|vignette|upright=3|Micro-architecture Zen 1 d'AMD.]]
Le passage à la micro-architecture n'a pas causé de grands changements. Le Zen 2 a ajouté une unité de calcul d'adresse, ce qui fait qu'on passe à 4 ALU, 3 AGU et 4 FPU. La fenêtre d'instruction flottante reste la même. Par contre, les fenêtres d'instruction entières changent un peu. Ou plutôt devrais-je dire les fenêtres d'instruction mémoire. En effet, le Zen 2 fusionne les fenêtres d'instructions liées aux AGU en une seule fenêtre d'instruction deux fois plus grosse.
Le Zen 5 a ajouté deux autres ALU entières et une unité de calcul d'adresse (6 ALU / 4 AGU)
==La microarchitecture Netburst du Pentium 4==
Dans cette section, nous allons voir l'architecture du processeur Pentium 4, qu'on a volontairement laissée de côté précédemment. Pourquoi un tel saut dans le temps ? Parce que le Pentium est complément à part des autres architectures Intel. Le Pentium 4 a représenté une rupture en termes de microarchitecture, qui a été un échec tellement retentissant que les processeurs suivants sont repartis sur la base du Pentium 3.
Il introduisait de nombreuses nouveautés architecturales qui étaient très innovantes. Par exemple, il introduisait le renommage avec un banc de registre physique, qui a été utilisé sur tous les processeurs Intel suivants. Mais la plupart de ces innovations étaient en réalité de fausses bonnes idées, ou du moins des idées difficiles à exploiter. Par exemple, le système de pipeline à ''replay'' n'a été utilisé que sur le Pentium 4 et aucun autre processeur ne l'a implémenté.
===Un focus sur la fréquence d'horloge===
La microarchitecture du Pentium 4 a été déclinée en plusieurs versions, dont les finesses de gravure n'étaient pas les mêmes. La microarchitecture Netburst, utilisée sur le Pentium 4, utilisait un pipeline à 20 étage, augmenté à 32 sur une révision ultérieure. Il a existé quatre révisions de l'architecture : Willamette (180 nm), Northwood (130 nm), Prescott (90 nm) et Cedar Mill (65 nm).
Un point important est que le Pentium 4 était prévu pour fonctionner à haute fréquence. Ses 1,5 GHz étaient impressionnants pour l'époque, les autres processeurs tournant à une fréquence proche du GigaHertzs. Pour cela, la solution retenue par Intel a été un pipeline très long, avec beaucoup d'étages. Le Pentium 4 a été décliné en plusieurs versions assez proches, chacune avec sa propre finesse de gravure qui n'ont pas toute le même pipeline. Les micro-architectures ''Willamette'' et ''Northwood'' avaient un pipeline de 20 étages, alors que les autres processeurs de l'époque avaient entre 10 et 15 étages maximum. Les micro-architectures ''Prescott'' et ''Cedar Mill'' étaient une refonte qui a fait grimper le nombre d'étages à 31 ! Du jamais vu, il s'agit d'un record pour un processeur commercial.
Un pipeline aussi long permet d'exécuter beaucoup d’instructions en même temps, chacune dans un étage, mais aussi d'atteindre de hautes fréquences facilement. Le problème est qu'un pipeline avec autant d'étages a beaucoup de problèmes. Un point important est que la prédiction de branchement est cruciale. Pour rappel, la pénalité en cas de mauvaise prédiction dépend du nombre d'étages avant que le branchement soit résolu. Et les branchements sont résolus soit en fin de décodage, soit dans l'unité de calcul. C'est à dire au milieu du pipeline, soit en fin de pipeline. La pénalité en cas de mauvaise prédiction de branchement était énorme sur le Pentium 4, elle atteignait facilement 30 cycles
Pour compenser, le Pentium 4 avait une prédiction de branchement très performante, pour l'époque. J'insiste sur le pour l'époque. Il utilisait un prédicteur qu'on a déjà abordé dans le chapitre sur la prédiction de branchement, précisément un prédicteur adaptatif à deux niveaux avec un historique global de 16 bits. Il avait aussi un ''Branch Target Buffer'' de 4096 entrées. Mais surtout, il intégrait une sorte de précurseur du cache de micro-opération, appelé le cache de traces, qui est détaillé dans la section suivante.
===Le cache de trace du Pentium 4===
Les décodeurs du Pentium 4 ne font pas décoder les instructions, ils mémorisent le résultat dans un cache de micro-opération un peu particulier, appelé le '''cache de trace'''. Une ligne de cache peut mémoriser 6 micro-opérations, ce qui peu sembler peu mais a été repris sur les micro-architectures suivantes. Mais le cache de trace a une grande différence avec un cache de micro-opération normal. Un cache de micro-opération normal mémorise une instruction par ligne de cache. Une instruction est décodée en plusieurs micro-instructions, qui sont enregistrées dans une ligne de cache. Si l'instruction n'utilise par les 6 micro-opérations disponibles, le reste de la ligne de cache n'est pas utilisé. Mais le Pentium 4 optimise le tout de manière ce à ce que ne soit pas le cas.
Sur le Pentium 4, la contrainte du "une instruction par ligne de cache" est abandonnée. Une ligne de cache mémorise 6 micro-opérations consécutives, qui peuvent appartenir à plusieurs instructions. Par exemple, si le décodeur décode 4 instructions consécutives en 6 micro-opérations au total, alors le tout prendra une seule ligne de cache sur le Pentium 4. Et les 4 instructions consécutives n'ont même pas à être consécutives en mémoire : il peut y avoir des branchements pris entre ces instructions !
{|class="wikitable"
|+ Cache de trace
|-
! Ligne de cache
| ADD || SUB || ADD || MOV || MUL || ''shift''
|-
! Ligne de cache
| colspan="3" | ADD ''load-up'' || MUL || colspan="2" | Branch if Equal
|-
! Ligne de cache
| XOR || colspan="4" | POP || SUB
|-
! ...
| colspan="6" | ...
|}
Pour expliquer cela plus concrètement, nous allons devoir introduire les concepts de trace et de bloc de base. Un '''bloc de base''' (''basic block'') est une suite d'instructions sans branchement, qui est séparé par deux branchements. Le début d'un bloc de base est la destination d'un branchement, un bloc de base se termine avec un branchement. Une '''trace''' est formée en concaténant plusieurs blocs de base. Pour donner un exemple, regardez le code illustré ci-contre. Il est composé d'un bloc de base A, suivi par un bloc de base B, qui peut faire appel soit au bloc C, soit un bloc D. Un tel code peut donner deux traces : ABC ou ABD. La trace exécutée dépend du résultat du branchement qui choisit entre C et D.
Le cache de trace mémorise des traces de 6 micro-opérations consécutives. Les traces sont formées en sortie des décodeurs d'instruction, par de subtiles opérations mélangeant mémorisation, décalage et concaténation. Les circuits qui construisent les traces ne sont pas connus, mais ils doivent certainement être très compliqués. toujours est-il qu'un cache de trace peut mémoriser des traces différentes, même si leur début est le même. Par exemple, prenons deux traces, composées des blocs de base A, B, C et D. La première trace est la trace ABC, la seconde est la trace ABD. Les deux traces auront chacune une ligne de cache dédiée.
Une trace est réutilisable quand le premier bloc de base est identique et que les prédictions de branchement restent identiques. Pour vérifier cela, le tag du cache de traces contient l'adresse du premier bloc de base, la position des branchements dans la trace et le résultat des prédictions utilisées pour construire la trace. Le résultat des prédictions de branchement de la trace est stocké sous la forme d'une suite de bits : si la trace contient n branchements, le n-ième bit vaut 1 si ce branchement a été pris, et 0 sinon. Même chose pour la position des branchements dans la trace : le bit numéro n indique si la n-ième instruction de la trace est un branchement : si c'est le cas, il vaut 1, et 0 sinon.
Si la trace est réutilisée par la suite, elle est lue depuis le cache de traces. Pour savoir si une trace est réutilisable, l'unité de chargement envoie le ''program counter'' au cache de traces, l'unité de prédiction de branchement fournit le reste des informations. Si on a un succès de cache de traces, et la trace est envoyée directement au décodeur. Sinon, la trace est chargée depuis le cache d'instructions et assemblée. Il faut signaler que le cache de trace avait sa propre unité de prédiction de branchement séparée de l'unité de prédiction de branchement normale.
[[File:TraceCache.png|centre|vignette|upright=2|Cache de traces.]]
Le cache de traces réduisait la longueur du pipeline en cas de succès de cache de trace. Quand les instructions étaient lues depuis le cache de trace, les étages avant le cache de trace ne sont pas utilisés, tout se passe comme s'ils étaient retirés du pipeline. C'est la même chose avec le cache de micro-opération des processeurs modernes, mais l'idée n'existait pas encore à l'époque. Le cache de trace mémorise des traces décodées, ce qui fait qu'un succès de cache de trace contournait non seulement le cache d'instruction, mais aussi les décodeurs. Le temps d'accès au cache de trace pouvait être assez élevé, même s'il était comparable au temps d'accès du cache d'instruction.
Le cache de traces a depuis été remplacé par une alternative bien plus intéressante, le cache de micro-opérations, plus flexible et plus performant. Comparé à un cache de trace, la contrainte "une instruction par ligne de cache" simplifie grandement l'implémentation d'un cache de micro-opération. Ne parlons pas de la détection des succès de cache, qui demande d'utiliser les prédictions de branchement. Mais le vrai problème avec le cache de trace est tout autre. Il arrive souvent qu'une micro-opération soit présente dans plusieurs lignes de cache en raison du processus de construction des traces, chose impossible avec un cache de micro-opération. Et c'est un problème, qui réduit la capacité effective du cache de trace. Alors certes, une ligne de cache est plus remplie que sur un cache de micro-opération, on est certain que les 6 micro-opération par ligne de cache sont remplies. Mais la redondance réduit grandement cet avantage.
===L'exécution dans le désordre et le chemin de données du P4===
Le renommage de registres se fait avec un banc de registres physiques avec une table d'alias.
Le Pentium 4 avait une exécution dans le désordre très limitée, basée sur la présence de deux files de micro-opération : une pour les accès mémoire, une autre pour les autres instructions. La seconde file regroupait opérations entières et flottantes, elles n'étaient pas séparées. Avec ces deux files, les instructions mémoire étaient exécutées dans l'ordre du programme, les instructions arithmétiques s'exécutaient aussi dans l'ordre du programme, mais une instruction arithmétique pouvait passer avant une instruction mémoire et inversement. L'avantage est que cela permettait de faire des lectures en avance, c'était une forme limitée de lecture non-bloquantes.
Il s'agit bel et bien de deux files d'instructions, pas de fenêtres d'instruction ni de stations de réservation. Le Pentium 4 est le seul processeur commercial qui a utilisé des files de micro-opération séparées, tous les autres utilisent des fenêtres d'instruction : centralisées pour Intel, décentralisées pour AMD (en général). Le Pentium 2 et 3, bien qu'antérieurs, utilisait une station de réservation unique. Cela peut sembler être un retour en arrière, mais les files d'instructions sont bien plus larges : de 42 micro-opérations pour le Pentium 3, on passe à 120 micro-opérations pour le Pentium 4. Et vu la longueur du pipeline, qui fait qu'il y a plus d'instructions en vol, c'était une nécessité. Mais cela n'aurait pas été possible en utilisant des stations de réservation, pour des raisons de consommation électrique et/ou de budget en transistors, ce qui fait que passer à une file de micro-opération été la solution retenue.
Pour les accès mémoire, le Pentium 4 utilisait donc une file de µops mémoire unique, couplée à une file d'écriture (non-représentée sur les schémas qui suivent). La file d'écriture du Pentium 4 était de 24 écritures maximum et gérait le ''Store-to-load forwarding''. Le processeur pouvait émettre une lecture et une écriture à chaque cycle. Il y avait un port d'émission pour les lectures et un autre pour les écritures, tous deux ayant chacun leur propre unité de calcul d'adresse.
Le processeur contenait 3 ALU entières, 2 unités de calcul d'adresse et une FPU. La FPU était complétée par une unité pour faire des copies entre registres flottants, des opérations MOV. Pour les AGU, il y en avait une dédiée aux lectures, une autre pour les écritures. Le tout était relié aux ports d'émissions comme suit :
[[File:Ports d'émission du Pentium 4.png|centre|vignette|upright=2.5|Ports d'émission du Pentium 4]]
Le processeur utilisait deux réseaux de contournement séparés : un pour les opérations flottantes, un pour les opérations entières. Le réseau de contournement pour les opérations entières est aussi relié aux unités de calcul d'adresse. Jusque là, rien de surprenant, le chemin de données du processeur est assez classique.
[[File:Architettura Pentium 4.png|centre|vignette|upright=3|Microarchitecture du Pentium 4.]]
===Les unités de calcul entières du Pentium 4===
Sur le Pentium 4, les ALU entières étaient cadencées à une fréquence double de celle du processeur. Les ALU entières pouvaient exécuter deux micro-opérations par cycle, ce qui fait que les ports d'émissions reliés aux ALU devaient eux aussi fonctionner à double fréquence. Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU.
Précisons que seules les ALU entières étaient à double fréquence, pas le multiplieur, pas le ''barrel shifter''. Pour simplifier, nous allons parler d'additionneur plutôt que de l'ALU entière, ce qui sera plus proche de la réalité. Et l'implémentation de l'additionneur du Pentium 4 était très innovante. L'additionneur pouvait exécuter deux additions par cycle, même si les deux additions ont une dépendance. Mais n'allez pas croire que l'implémentation était intuitive, avec un additionneur 32 bit basique très rapide. Non seulement l'additionneur fonctionnait à double fréquence, mais il était aussi pipeliné, avec un système de contournement interne !
Les additionneurs étaient pipelinées, d'une manière très simple. Une addition 32 bits était découpée en trois étapes : deux additions de 16 bits, une dernière étape pour mettre à jour le registre d'état. Pour cela, chaque additionneur était composé de deux additionneur 16 bits chacune, placées l'une après l'autre, avec un registre de pipeline entre les deux. L'additionneur prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible, le second calculait les 16 bits de poids fort lors du second cycle. Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Une addition se fait donc en deux étapes, sauf que c'est compensé par le fait que l'additionneur fonctionnait à une fréquence double de celle du processeur ! Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. Deux additions consécutives s'exécutaient donc en 1 cycle et demi, alors qu'on aurait cru au premier abord que cela prendrait seulement un cycle. Si on fait les calculs, on s'apercoit que le rythme de croisière est cependant proche de 2 additions par cycle, bien qu'inférieur. 3 additions consécutives se font en deux cycles, 5 additions en 3 cycles, 7 en 4 cycles, etc.
Et le Pentium 4 ajoutait un système de contournement interne à l'ALU. En clair, si une addition utilise le résultat de l'addition précédente, les deux peuvent s'exécuter en un cycle d'horloge et demi. Les 16 bits de poids faible de la première addition sont disponibles après un cycle ALU, ce qui permet de démarrer le calcul de la seconde addition au cycle suivant.
===Le ''replay pipeline''===
Le processeur est un processeur triple émission : il peut charger et décoder 3 µops par cycle, le ROB peut terminer 3 µops par cycle, etc. Pourtant, les ports d'émission peuvent émettre 6 instructions par cycle : 4 ports, dont deux à double fréquence. Une telle différence s'explique par l'usage d'un ''replay pipeline'', dont nous avons déjà parlé dans ce cours.
Pour rappel, le Pentium 4 suppose que les lectures font tous un succès de cache L1. Si une opération arithmétique utilise la donnée lue comme opérande, le processeur l'émet immédiatement, l'opérande sera disponible une fois l'opération en entrée de l'ALU entière. Mais s'il s'est trompé, le processeur ré-exécute l'instruction après un temps d'attente de quelques cycles, pour se caler sur la latence du cache L2. Et si il y a un défaut de cache L2, l’instruction attend encore. Cela demande de ré-exécuter des instructions émises à tord, ce qui fait que le processeur doit avoir la capacité d'exécution pour. Ce pourquoi le processeur peut émettre 6 µops dans les unités de calcul : 3 µops normales et 3 µops ré-exécutées.
==Les microarchitectures x86 de Cyrix et Via==
Les processeurs x86 grand public sont actuellement conçus et commercialisé par deux acteurs : Intel et AMD. Il faut dire que le jeu d'instruction x86 demande une licence pour être utilisée, qu'Intel et AMD refusent de dsitribuer à leurs concurrents, sauf en de très rares occasions. Cependant, il y a eu dans le passé un troisième acteur, qui a reussit à acquérir les droits de la licence x86 : Cyrix, qui a ensuite été racheté par National Semiconductor, lui-même acquis par Via.
===Les architectures compatibles 386 et 486 de Cyrix===
Cyrix a produits plusieurs CPUs et même des co-processeurs x87, avant d'être racheté. Sa carrière commence avec le Cyrix FasMath, un co-processeur arithmétique pour calculs flottants, compatible avec le x87 d'Intel. Il a ensuite produits plusieurs clones du processeur 386 d'Intel, nommés 486SLC et 486DLC. Une dénomination assez trompeuse, vous en conviendrez, mais qui n'est heureusement pas passée inaperçue et leur a valu de nombreuses critiques. Néanmoins, leurs processeurs étaient plus performants que le 386 de base, ce qui fait qu'ils étaient des upgrades corrects pour les PC avec un 386 (les cartes mères compatibles avec le i386 n’étaient pas compatibles avec le i486). Le 486SRX2 et le 486DRX2, sortis plus tard, étaient des versions avec une fréquence encore plus élevée, ce qui augmentait leurs performances.
Cyrix récidiva avec le 5x86, un processeur offrant des performances s'approchant du Pentium, mais pour la plateforme 486. Le processeur supportait seulement les instructions du 486, pas les instructions ajoutées sur le Pentium. Son bus était similaire à celui du 486. Mais sa microarchitecture se rapprochait du Pentium, bien que n'étant pas aussi complexe. Il y avait une file d'instruction capable de mettre en attente 3 instructions, un cache unifié, une unité de prédiction de branchement. Malheureusement, la prédiction de branchements avait été désactivée en raisons de bugs matériels qui faisaient crasher le processeur. Niveau ALU, mais il y avait une FPU, une ALU entière et une unité mémoire. Mais il n'y avait pas de double émission.
[[File:Cy5x86 arch (cropped).png|centre|vignette|upright=2.5|Cy5x86 arch (cropped).]]
===Le Cyrix 6x86 : une architecture superscalaire avancée===
Le '''Cyrix 6x86''' est là où la superscalarité apparait dans la gamme Cyrix. Le processeur était non pas une upgrade pour des cartes mères existantes, mais un concurrent direct du Pentium. C'était un processeur à double émission, avec exécution dans le désordre et renommage de registre.
L'organisation de ses caches est assez originale. Il avait un cache principal de 16 kibioctets, qui mémorisait à la fois données et instructions, couplé à un cache d'instruction de 256 octets. En termes modernes, on dirait qu'il disposait d'un cache L2, couplé à un cache L1 d'instructions, mais sans cache L1 pour les données. Le cache unifié/L2 était un cache double port. Le cache d'instruction était totalement associatif.
[[File:Cy6x86 arch.png|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le processeur supportait une double émission bien plus complète que celle du Pentium. Il disposait de deux pipelines entiers, nommés X et Y. Le pipeline X était le seul à pouvoir exécuter branchements et opérations flottantes, c'était le seul connecté à la FPU. Par contre, il était possible d'émettre un branchement ou une opération flottante dans le pipeline X, tout en émettant une seconde instruction dans le pipeline Y. Ce que le Pentium ne pouvait pas faire. Certaines instructions ne pouvaient cependant pas être appariées avec une autre : les multiplications/divisions, les ''string instruction'', les ''far jumps'', les accès aux entrées-sorties, et des instructions systèmes.
La FPU pouvait exécuter des instructions en parallèle de l'ALU entière. Elle avait une file de µops de 4 µops flottantes. Elle avait aussi sa propre ''Store queue'', spécifiquement dédiée aux écritures flottantes, séparée de la ''Store Queue'' pour les écritures "normales", celles des registres généraux. Cette séparation est possible du fit que les registres flottants et généraux sont séparés sur le jeu d'instruction x86.
[[File:Cyrix 6x86 arch.svg|centre|vignette|upright=2.5|Cy6x86 microarchitecture.]]
Le Cyrix 6x86 avait de nombreux avantages sur le Pentium. Il intégrait notamment du renommage de registres pour les registres entiers, ainsi que le contournement, ce que ne faisait pas le Pentium. Et il pouvait faire de la double émission entière-flottante, alors que le Pentium ne pouvait pas émettre de seconde µop en parallèle avec une opération flottante. Tout cela le rendait bien plus performant que le Pentium, sur le papier. Sauf que sa FPU était peu performante, proche de celle du 5x86. Les concepteurs du processeur avaient mis le paquet sur les opérations entières, en délaissant les opérations flottantes. Les applications développées pour le Pentium, qui répartissaient au mieux leurs calculs entre FPU et ALU entière, marchaient donc moins bien sur le 6x86.
Le successeur du 6x86, le '''Cyrix Jalapeno''', était un concurrent du Pentium 2/3. Son pipeline passait de 7 à 11 étages, ce qui permettait des augmentation de fréquence importante. Son cache était quadruplé en taille et passait à 8 voies. Et pour ce qui de la superscalarité, il était maintenant capable d'émettre deux µops flottantes par cycle : une partait dans l'additionneur flottant, l'autre dans un multiplieur flottant.
===Les processeurs de marque Via===
Après deux rachats, la licence x86 de Cyrix est passée dans les main de Via. Via avait racheté Cyrix, mais aussi Centaur, un autre concepteur de processeur. Via produisit plusieurs processeurs x86, basés sur des design provenant non pas de Cyrix, mais de Centaur, pour leur microarchitecture. Le premier processeur de cette lignée, le mal-nommé Cyrix III, a d'ailleurs été renommé en C3, parce qu'il n'avait aucun lien avec les microarchitectures de Cyrix et n'utilisait pas leurs technologies.
Les '''processeurs Via C3''' et '''Via C7''' étaient des processeurs 32 bits. Ils étaient prévus pour des ordinateurs portables et ciblainet une faible consommation d'énergie, plutôt que des hautes performances. Ils n'avaient pas d'exécution dans le désordre, mais utilisaient une superscalarité limitée à la double émission.
Le C3 regroupe en réalité deux microarchitecture semblables. Les modèles Samuel 2 et Ezra utilisaient la première version de la microarchitecture, alors que les processeurs Nehemiah avaient une version améliorée de celle-ci. Le pipeline passait de 12 à 16 étages, sa FPU est améliorée, les extensions SIMD sont revues (passage au SSE, abandon du 3DNow!). Les processeurs C7 utilisent cette même microarchitecture, bien qu'améliorée sur quelques détails.
[[File:VIA Nano Architecture Blockdiagram.jpg|vignette|upright=1.5|VIA Nano Architecture.]]
Par la suite, Via sortit le '''Via Nano''' en 2008 . C'était le premier CPU 64 bits de la marque. Il avait tout d'un design moderne, avec exécution dans le désordre, prédiction de branchement complexe, hiérarchie de cache classique, préchargement, etc. Il implémentait la superscalarité en triple émission. Pour cela, son ''front-end'' chargeait 16 octets par cycle, qui étaient accumulés dans une file d'instruction. Trois décodeurs lisaient les instructions dans cette file, puis envoyaient trois µops dans une file de µops de taille inconnue. Le renommage de registre pouvait lui aussi renommer trois µops par cycle.
Niveaux unités, il avait deux ALU entières, une unité LOAD, une unité STORE, deux unités SIMD (une pour les additions SIMD, une pour les multiplications SIMD). Chaque unité avait sa propre file de µops, qui faisait entre 8 et 12 entrées. Les unités LOAD et STORE avaient chacune leur propre file de µops, il n'y avait pas de file de µops partagée entre les deux. L'unité LOAD alimentait une ''load queue'' de 16 lectures, alors que l'unité STORE alimentait une ''store queue'' de 16 écritures. Les unités de calcul sont décrites dans le tableau ci-dessous :
{|class="wikitable"
|-
! !! ALU 1 !! ALU 2 !! FPU/SIMD 1 !! FPU/SIMD 2 !! LOAD !! STORE
|-
! File de µops
| 12 entrées || 11 entrées || 8 entrées || 24 entrées || 8 entrées || 8 à 13 entrées, selon les sources
|-
! rowspan="5" | Opérations
| Opérations entières de base || Opérations entières de base || || || LOAD || STORE
|-
| Décalages et rotations || Multiplication entière 32 bits || Multiplication entière 64 bits || || ||
|-
| CMOV || CMOV || Division entière 64 bits || Division entière 64 bits || ||
|-
| ''Add With Carry'' || Branchements || Division flottante || || ||
|-
| || || SIMD opérations entières + FMUL 128 bits || SIMD opérations entières + FADD 128 bits || ||
|}
Les processeurs qui ont suivis sont des processeurs commercialisés uniquement en Chine, à destination du marché chinois et subventionnés (si ce n'est plus), par l'administration chinoise.Le peu d'informations qui sont disponibles dessus sont disponibles sur le substack ''Chips and cheese'', qui a notamment quelques articles sur les processeurs Longsoon. Pour une histoire complète et une review en profondeur des architectures de Via, je conseille les articles du blog ''Chips and cheese'' :
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-the-3rd-player-in-the-modern-x86-market The Weird and Wacky World of VIA, the 3rd player in the “Modern” x86 market]
* [https://chipsandcheese.com/p/the-weird-and-wacky-world-of-via-part-2-zhaoxins-not-quite-electric-boogaloo The Weird and Wacky World of VIA Part 2: Zhaoxin’s not quite Electric Boogaloo]
* [https://chipsandcheese.com/p/zhaoxin-part-3-a-sort-of-anti-climax Zhaoxin Part 3: A Sort of Anti-Climax]
* [https://chipsandcheese.com/p/via-part-4-a-deep-dive-into-centaurs-last-cpu-core-cns VIA Part 4 – A Deep Dive into Centaur’s Last CPU Core: CNS]
* [https://chipsandcheese.com/p/examining-centaur-chas-die-and-implementation-goals Examining Centaur CHA’s Die and Implementation Goals]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les processeurs superscalaires
| prevText=Les processeurs superscalaires
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
3iv2pzwfskr2mnk2mpstm967ly3wyr6
Discussion Wikilivres:Le Bistro/2026
5
83406
773437
773270
2026-09-28T10:49:03Z
MediaWiki message delivery
36013
/* Actualités techniques n° 2026-40 */ nouvelle section
773437
wikitext
text/x-wiki
== Actualités techniques n° 2026-03 ==
<section begin="technews-2026-W03"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/03|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* La Fondation Wikimedia a publié des questions directrices pour son plan annuel de juillet 2026 à juin 2027 sur les plateformes [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] et ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. Celles-ci portent sur les tendances mondiales, une expérimentation plus rapide et plus constructive, un meilleur accompagnement des nouveaux contributeurs, le renforcement du rôle des éditeurs et des utilisateurs avancés, l'amélioration de la collaboration entre les projets, ainsi que le développement et la fidélisation du lectorat. Des commentaires et suggestions sont les bienvenus sur la [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|page de discussion]].
'''Actualités pour la contribution'''
* Dans le cadre des travaux en cours de l'équipe technique communautaire sur le projet [[m:Special:MyLanguage/Community Wishlist/W372|Listes de surveillance multiples]], l'affichage de [[Special:EditWatchlist|Modifier la liste de surveillance]] sera mis à jour entant que qu'une première étape vers la prise en charge de plusieurs listes de surveillance. De plus, la pagination de [[Special:Search|Recherche]] sera également mise à jour, dans le cadre du travail sur le souhait [[m:Special:MyLanguage/Community Wishlist/W186|Refonte de la pagination / navigation des pages]]. [https://phabricator.wikimedia.org/T411596]
* [[m:Special:GlobalWatchlist|La Liste de Surveillance Globale]] est une [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] de MediaWiki qui vous permet de voir vos listes de surveillance provenant de différents wikis sur la même page. Il a récemment été mis à jour pour ressembler davantage à la [[Special:Watchlist|Liste de surveillance]] régulière, par exemple en le préparant pour les comptes temporaires dans le masquage IP (y compris le réacheminement des liens des utilisateurs vers les pages de contributions), en mettant les titres de page en gras et en ouvrant les liens dans les résumés d'édition et les balises dans de nouveaux onglets du navigateur. [https://phabricator.wikimedia.org/T398361][https://phabricator.wikimedia.org/T298919][https://phabricator.wikimedia.org/T273526][https://phabricator.wikimedia.org/T286309]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:28|la tâche soumise|les {{formatnum:28}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:28||s}} la semaine dernière]]. Par exemple, le problème selon lequel les blocs globaux ne disposaient pas de l'option permettant de désactiver l'envoi d'e-mails a maintenant été résolu et sera disponible à l'utilisation à partir de la semaine du 13 janvier. [https://phabricator.wikimedia.org/T401293]
'''Actualités pour la contribution technique'''
* L'[[mw:Special:MyLanguage/VisualEditor/Citation tool|outil de citation VisualEditor]] et les [[mw:Special:MyLanguage/Help:Reference Previews|Aperçus de référence]] prennent désormais en charge "carte" comme type de référence. [https://phabricator.wikimedia.org/T411083]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.10|MediaWiki]]/[[mw:MediaWiki 1.46/wmf.11|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/03|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W03"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 12 janvier 2026 à 20:33 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29907192 -->
== Thank You for Last Year – Join Wiki Loves Ramadan 2026 ==
Dear Wikimedia communities,
We hope you are doing well, and we wish you a happy New Year.
''Last year, we captured light. This year, we’ll capture legacy.''
In 2025, communities around the world shared the glow of Ramadan nights and the warmth of collective iftars. In 2026, ''Wiki Loves Ramadan'' is expanding, bringing more stories, more cultures, and deeper global connections across Wikimedia projects.
We invite you to explore the ''Wiki Loves Ramadan 2026'' [[m:Special:MyLanguage/Wiki Loves Ramadan 2026|Meta page]] to learn how you can participate and [[m:Special:MyLanguage/Wiki Loves Ramadan 2026/Participating communities|sign up]] your community.
📷 ''Photo campaign on '' [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan 2026|Wikimedia Commons]]
If you have questions about the project, please refer to the FAQs:
* [[m:Special:MyLanguage/Wiki Loves Ramadan/FAQ/|Meta-Wiki]]
* [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan/FAQ|Wikimedia Commons]]
''Early registration for updates is now open via the '''[[m:Special:RegisterForEvent/2710|Event page]]'''''
''Stay connected and receive updates:''
* [https://t.me/WikiLovesRamadan Telegram channel]
* [https://lists.wikimedia.org/postorius/lists/wikilovesramadan.lists.wikimedia.org/ Mailing list]
We look forward to collaborating with you and your community.
'''The Wiki Loves Ramadan 2026 Organizing Team''' 16 janvier 2026 à 20:44 (CET)
<!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29879549 -->
== <span lang="en" dir="ltr">Tech News: 2026-04</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W04"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/04|Translations]] are available.
'''Updates for editors'''
* The tray shown on [[Special:Diff|Special:Diff]] in mobile view has been redesigned. It is now collapsed by default, and incorporates a link to undo the edit being viewed, making it easier for mobile editors and reviewers to take action while keeping the interface uncluttered. [https://phabricator.wikimedia.org/T402297]
* [[m:Special:GlobalWatchlist|The Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continues to improve — it now automatically determines the text direction (ensuring correct display of sites with unusual domain names) and shows detailed descriptions for log actions. Later this week, a new permanent link for page creations and CSS classes for each entry element will be added. [https://phabricator.wikimedia.org/T412505][https://phabricator.wikimedia.org/T287929][https://phabricator.wikimedia.org/T262768][https://phabricator.wikimedia.org/T414135]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:32}} community-submitted {{PLURAL:32|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the previously observed issue in Vector 2022, where anchor link targets were obscured by the sticky header, has now been addressed. [https://phabricator.wikimedia.org/T406114]
'''Updates for technical contributors'''
* As mentioned in the [[m:Special:MyLanguage/Tech/News/2025/44|October 2025 deprecation announcement]], MediaWiki Interfaces team will begin sunsetting all transform endpoints containing a trailing slash from the MediaWiki REST API the week of January 26. Changes are expected to roll out to all wikis on or before January 30th. All API users currently calling them are encouraged to transition to the non-trailing slash versions. Both endpoint variations can be found, compared, and tested using the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox]. If you have questions or encounter any problems, please file a ticket in Phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board].
* Interactive reference documentation for the [[mw:Special:MyLanguage/Wikimedia REST API|Wikimedia REST API]] has moved. Requests to API docs previously hosted through [[mw:Special:MyLanguage/RESTBase|RESTBase]] (e.g.: <code dir=ltr>https://en.wikipedia.org/api/rest_v1/</code>) are now redirected to the [[w:en:Special:RestSandbox|REST Sandbox]].
* The [[mw:Special:MyLanguage/Wikidata Platform|WMF Wikidata Platform team]] (WDP) has published its [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|January 2026 newsletter]]. It includes updates on the legacy full-graph endpoint decommissioning, the User-Agent policy change, the monthly Blazegraph migration office hours, and efforts to reduce regressions caused by the legacy endpoint shutdown. As a reminder, you can [[m:Special:MyLanguage/Global message delivery/Targets/WDP team updates|subscribe to the WDP newsletter]]!
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.12|MediaWiki]]
'''Meetings and events'''
* The [[mw:Wikimedia Hackathon Northwestern Europe 2026|Wikimedia Hackathon Northwestern Europe 2026]] will take place on 13-14 March 2026 in Arnhem, the Netherlands. Applications opened mid-December and will close soon or when capacity is reached. It's a two-day, technically oriented hackathon bringing together Wikimedians from the region. Hope to see you there!
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/04|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W04"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 19 janvier 2026 à 21:29 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29943403 -->
== Révision annuelle du code universel de conduite et des lignes directrices de l'application ==
<section begin="announcement-content" />
Nous vous informons que la période de relecture annuelle du Code de conduite universel et des règles d'applications est actuellement ouverte. Vous pouvez faire vos commentaires sur les modifications que vous souhaitez apporter jusqu'au 9 février 2026. C'est la première d'une série d'étapes nécessaires pour la révision annuelle. Vous trouverez [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2026|d'autres informations et les discussions auxquelles participer sur la page UCoC de Meta]].
Le [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Comité de coordination du code universel de conduite]] (U4C — Universal Code of Conduct Coordinating Committee) est un groupe global dont le rôle est de fournir une implémentation équitable et cohérente de l'UCoC. Cette relecture annuelle a été envisagée et mise en place par l'U4C. Pour plus d'informations et les responsabilités de l'U4C, veuillez lire la [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|Charte de l'U4C]].
Veuillez partager ces informations avec les autres membres concernés de votre communauté.
-- En coopération avec l'U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|discussion]])<section end="announcement-content" />
19 janvier 2026 à 22:01 (CET)
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=29905753 -->
== Actualités techniques n° 2026-05 ==
<section begin="technews-2026-W05"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/05|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* La Fondation Wikimedia invite à donner des commentaires sur [[m:Special:MyLanguage/Product and Technology Advisory Council/Year1 Reflections and Proposed Way Forward 2026 Update|l’avenir proposé]] du [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif des produits et technologies]] jusqu’au 28 février.
* Tous les utilisateurs disposant d'un compte enregistré peuvent désormais utiliser des clés d'accès pour la [[m:Special:MyLanguage/Help:Two-factor authentication|double authentification]] (2FA). Les clés d'accès sont un moyen simple de se connecter sans utiliser un second appareil. Elles vérifient l'identité de l'utilisateur à l'aide d'une empreinte digitale, d'une reconnaissance faciale ou d'un code PIN. Pour configurer une clé d'accès, configurez d'abord une méthode 2FA classique. Actuellement, pour se connecter avec une clé d'accès, les utilisateurs doivent également utiliser un mot de passe. Plus tard ce trimestre, la connexion sans mot de passe permettra aux utilisateurs de se connecter d'un simple clic avec une clé d'accès. Les utilisateurs disposant de droits avancés devront également avoir la 2FA activée. Cela fait partie du projet [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security|Sécurité du compte]].
* Les contributeurs non enregistrés sur des IP bloquées ou des plages d'IP bloquées peuvent désormais interagir sur le wiki pour faire appel d'un blocage en créant un compte temporaire afin de contester un blocage sur la page de discussion de l'utilisateur, sauf si l'option « empêcher cet utilisateur de modifier sa propre page de discussion » est activée. Cela résout le problème des utilisateurs déconnectés incapables d'utiliser le processus de déblocage par défaut via la page de discussion de l'utilisateur. [https://phabricator.wikimedia.org/T398673]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]]. Par exemple, la description des méthodes d'authentification à deux facteurs (2FA) sur la page de gestion a été mise à jour. Il est désormais plus clair et plus facile pour les utilisateurs à comprendre et à utiliser. [https://phabricator.wikimedia.org/T332385]
'''Actualités pour la contribution technique'''
* Une nouvelle variable AbuseFilter, <code>account_type</code>, a été ajoutée pour fournir un moyen fiable de déterminer le type de compte créé dans les actions <code>createaccount</code> et <code>autocreateaccount</code>. Dans le cadre de ce changement, la variable <code>accountname</code> a été renommée en <code>account_name</code>, et <code>accountname</code> est désormais obsolète. Les gestionnaires de filtres doivent mettre à jour tous les filtres qui utilisent des vérifications de type de compte codées en dur ou la variable obsolète. [https://phabricator.wikimedia.org/T414049]
* Les vignettes d'images demandées dans des tailles non standard, et en utilisant des méthodes non standard telles que les requêtes directes à <code dir=ltr><nowiki>upload.wikimedia.org/…</nowiki></code>, cesseront de fonctionner dans un proche avenir. Ce changement vise à prévenir les abus externes continus par des robots et des aspirateurs web. Certains utilisateurs ayant des CSS/JS personnalisés, les administrateurs d'interface qui peuvent corriger les gadgets et les thèmes locaux, ainsi que les auteurs d'outils, devront mettre à jour leur code pour utiliser des tailles de vignettes standard. [[phab:T414805|Des détails, des liens de recherche et des exemples de correction sont disponibles dans la tâche]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.13|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/05|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W05"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 26 janvier 2026 à 22:17 (CET)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29969530 -->
== <span lang="en" dir="ltr">Tech News: 2026-06</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W06"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/06|Translations]] are available.
'''Updates for editors'''
* The "{{int:pageinfo-toolboxlink}}" feature, which gives validating information about a page ([{{fullurl:{{FULLPAGENAME}}|action=info}} example]), now automatically includes a table of contents. If there is a local [[{{ns:8}}:Pageinfo-header]] page created by individual users, it can now be removed. [https://phabricator.wikimedia.org/T363726]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, VisualEditor previously added bold or italic formatting inside link descriptions, making the wikicode complex. This has now been fixed. [https://phabricator.wikimedia.org/T409669]
'''Updates for technical contributors'''
* There was no XML dump on 20 January. Additionally, from now on, dumps will be generated once per month only. [https://phabricator.wikimedia.org/T414389]
* The MediaWiki Interfaces team removed support for all transform endpoints containing a trailing slash from the [https://www.mediawiki.org/wiki/Special:MyLanguage/API:REST%20API MediaWiki REST API]. All API users currently calling those endpoints are encouraged to transition to the non-trailing slash versions. If you have questions or encounter any problems, please file a ticket in phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.14|MediaWiki]]
'''Weekly highlight'''
* Users are reminded that the Wikimedia Foundation has shared some guiding questions for the July 2026–June 2027 Annual Plan on [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] and ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. These focus on global trends, faster and healthier experimentation, better support for newcomers, strengthening editors and advanced users, improving collaboration across projects, and growing and retaining readership. Feedback and ideas are welcome on the [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|talk page]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/06|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W06"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 février 2026 à 18:43 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30000986 -->
== Actualités techniques n° 2026-07 ==
<section begin="technews-2026-W07"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/07|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] Les contributeurs connectés qui gèrent de grandes ou complexes listes de suivi peuvent désormais organiser et filtrer les pages surveillées de manière à améliorer leurs flux de travail grâce à la nouvelle fonctionnalité [[mw:Special:MyLanguage/Help:Watchlist labels|Étiquettes de liste de suivi]]. En ajoutant des étiquettes personnalisées (par exemple : pages que vous avez créées, pages surveillées pour vandalisme, ou pages de discussion), les utilisateurs peuvent identifier plus rapidement ce qui nécessite une attention, réduire la charge cognitive et répondre plus efficacement. Cela améliore l'utilisabilité de la liste de suivi, en particulier pour les éditeurs très actifs.
* Une nouvelle fonctionnalité disponible sur [[Special:Contributions|Special:Contributions]] montre [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts|des comptes temporaires]] qui sont probablement utilisés par la même personne, et rend ainsi le patrouillage moins chronophage. En vérifiant les contributions d'un compte temporaire, les utilisateurs ayant accès aux adresses IP des comptes temporaires peuvent désormais avoir une vue des contributions des comptes temporaires associés. La fonctionnalité recherche toutes les adresses IP associées à un compte temporaire donné pendant la période de conservation des données et affiche toutes les contributions de tous les comptes temporaires ayant utilisé ces adresses IP. [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts#February 2026: Improvements to the patroller tooling|Plus...]] [https://phabricator.wikimedia.org/T415674]
* Lorsque les éditeurs prévisualisent une modification de wikitexte, la boîte de rappel indiquant qu'ils ne voient qu'une prévisualisation (qui est affichée en haut) a désormais un fond gris/neutre au lieu d'un fond jaune/d'avertissement. Cela facilite la distinction entre les notes de prévisualisation et les avertissements réels (par exemple, les conflits de modification ou les cibles de redirection problématiques), qui seront désormais affichés dans des boîtes d'avertissement ou d'erreur séparées. [https://phabricator.wikimedia.org/T414742]
* La [[m:Special:GlobalWatchlist|Liste de suivi globale]] vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page. L' [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continue de s'améliorer — elle prend désormais en charge correctement plus d'un site Wikibase, par exemple à la fois [[d:|Wikidata]] et [[testwikidata:|testwikidata]]. De plus, des problèmes concernant la direction du texte ont été résolus pour les utilisateurs qui préfèrent Wikidata ou d'autres sites Wikibase dans des langues de droite à gauche (RTL). [https://phabricator.wikimedia.org/T415440][https://phabricator.wikimedia.org/T415458]
* <span lang="en" dir="ltr" class="mw-content-ltr">The automatic "magic links" for ISBN, RFC, and PMID numbers have been [[mw:Special:MyLanguage/Help:Magic links|deprecated in wikitext since 2021]] due to inflexibility and difficulties with localization. Several wikis have successfully replaced RFC and PMID magic links with equivalent external links, but a template was often required to replace the functionality of the ISBN magic link. There is now a new [[mw:Special:MyLanguage/Help:Magic words#isbn|built-in parser function]] <code dir=ltr><nowiki>{{#isbn}}</nowiki></code> available to replace the basic functionality of the ISBN magic link. This makes it easier for wikis who wish to migrate off of the deprecated magic link functionality to do so.</span> [https://phabricator.wikimedia.org/T145604]
* Deux nouveaux wikis ont été créés :
** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q35401|Jju]] ([[w:kaj:|<code>w:kaj:</code>]]) [https://phabricator.wikimedia.org/T413283]
** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q1186896|Nawat]] ([[w:ppl:|<code>w:ppl:</code>]]) [https://phabricator.wikimedia.org/T413273]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* Un nouveau groupe d'utilisateurs global a été créé : [[{{int:grouppage-local-bot}}|{{int:group-local-bot}}]]. Il sera utilisé en interne par le logiciel pour permettre aux robots communautaires de contourner les limites de débit appliquées aux [[w:en:Web_scraping|web scrapers]] abusifs. Les comptes approuvés en tant que robots sur au moins un wiki Wikimedia seront automatiquement ajoutés à ce groupe. Cela ne changera pas les autorisations dont dispose le robot. [https://phabricator.wikimedia.org/T415588]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.15|MediaWiki]]
'''Rencontres et évènements'''
* La [[mw:Special:MyLanguage/MediaWiki Users and Developers Conference Spring 2026|Conférence des utilisateurs et des développeurs de MediaWiki, Printemps 2026]] se tiendra du 25 au 27 mars à Salt Lake City, États-Unis. Cet événement est organisé par et pour la communauté MediaWiki de tiers. Vous pouvez proposer des sessions et vous inscrire pour y assister. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/AZBWVI46SDEB65PGR5J6E4TYOQQEZXM7/]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/07|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W07"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 février 2026 à 00:30 (CET)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30026671 -->
== Actualités techniques n° 2026-08 ==
<section begin="technews-2026-W08"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/08|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* <span class="mw-translate-fuzzy">L'[[mw:Special:MyLanguage/Wikimedia Site Reliability Engineering|équipe SRE]] va procéder au nettoyage d'[[m:Special:MyLanguage/Etherpad|Etherpad]], l'éditeur web open source de documents collaboratifs en temps réel. Tous les blocs-notes seront définitivement supprimés après le 30 avril 2026 – si des projets de migration sont encore en cours à cette date, l'équipe pourra réexaminer la date au cas par cas. Veuillez effectuer des sauvegardes locales de tout contenu que vous souhaitez conserver, car les données supprimées ne pourront pas être récupérées. Ce nettoyage permet de réduire la taille de la base de données et l'empreinte de l'infrastructure. Etherpad continuera de prendre en charge la collaboration en temps réel, mais le stockage à long terme n'est plus assuré. D'autres nettoyages pourront avoir lieu ultérieurement sans préavis.</span> [https://phabricator.wikimedia.org/T415237]
'''Actualités pour la contribution'''
* L'équipe de Recherche d'Informations lancera une [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|expérimentation sur l'application mobile Android]], afin de tester des fonctionnalités de recherche hybrides capables de gérer à la fois les requêtes sémantiques et par mots-clés. L'amélioration de la recherche sur la plateforme permettra aux lecteurs de trouver plus facilement ce qu'ils cherchent, directement sur Wikipédia. L'expérimentation sera d'abord lancée sur Wikipédia en grec fin février, puis sur les versions anglaise, française et portugaise en mars. [https://diff.wikimedia.org/2026/01/08/semantic-search-making-it-easier-to-find-the-information-readers-want/ En savoir plus] sur le blog ''Diff''. [https://www.mediawiki.org/wiki/Readers/Information_Retrieval]
* L'équipe « Croissance des lecteurs » mènera [[mw:Special:MyLanguage/Readers/Reader Growth/WE3.10.2 Mobile Table of Contents|une expérience]] auprès des utilisateurs de la version mobile du site web qui ajoute une table des matières et développe automatiquement toutes les sections des articles, afin de mieux comprendre les problèmes de navigation qu'ils rencontrent. Le test sera disponible sur les versions arabe, chinoise, anglaise, française, indonésienne et vietnamienne de Wikipedia.
* Auparavant, les notifications ([[{{ns:8}}:Sitenotice]] et [[{{ns:8}}:Anonnotice]]) du site ne s'affichaient que sur la version ordinateur. Maintenant, elles s'afficheront désormais sur toutes les plateformes. Les utilisateurs mobiles verront ces notifications. Les administrateurs du site doivent être prêts à tester et à corriger les notifications sur les appareils mobiles afin d'éviter toute interférence avec les articles. Pour désactiver ces notifications, les administrateurs d'interface peuvent ajouter <code dir="ltr">#siteNotice { display: none; }</code> à [[{{ns:8}}:Minerva.css]]. [https://phabricator.wikimedia.org/T138572][https://phabricator.wikimedia.org/T416644]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]]. Par exemple, un problème concernant la section ''[[Special:RecentChanges|Spécial:Modifications récentes]]'' a été résolu. Auparavant, cliquer sur « Masquer » dans les filtres actifs entraînait la disparition du bouton « Afficher les nouvelles modifications depuis… », alors qu'il aurait dû rester visible. Ce bouton fonctionne désormais correctement. [https://phabricator.wikimedia.org/T406339]
'''Actualités pour la contribution technique'''
* Une nouvelle documentation est désormais disponible pour aider les rédacteurs à déboguer les fonctionnalités de recherche interne. Elle facilite le dépannage lorsque des pages n'apparaissent pas dans les résultats, lorsque le classement semble inattendu et lorsqu'il est nécessaire d'inspecter le contenu indexé, ce qui permet de mieux comprendre et d'analyser le comportement de la recherche. [[mw:Help:CirrusSearch/Debug|En savoir plus]]. [https://phabricator.wikimedia.org/T411169]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.16|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/08|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W08"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 février 2026 à 20:17 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30086330 -->
== <span lang="en" dir="ltr">Tech News: 2026-09</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W09"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/09|Translations]] are available.
'''Weekly highlight'''
* [[mw:Special:MyLanguage/Edit check/Reference Check|Reference Check]] has been deployed to English Wikipedia, completing its rollout across all Wikipedias. The feature prompts newcomers to add a citation before publishing new content, helping reduce common citation-related reverts and improve verifiability. In A/B testing, the impact was substantial: newcomers shown Reference Check were approximately 2.2 times more likely to include a reference on desktop and about 17.5 times more likely on mobile web. [https://analytics.wikimedia.org/published/reports/editing/reference_check_ab_test_report_final_2025.html]
'''Updates for editors'''
* The [[mw:Special:MyLanguage/Extension:InterwikiSorting|InterwikiSorting extension]], which allowed for the [[m:Special:MyLanguage/Interwiki sorting order|sorting of interwiki links]], has been undeployed from Wikipedia. As a result, editors who had enabled interwiki link sorting in non-compact mode (full list format) will now see links reordered. The links moving forward will be listed in the alphabetical order of language code. [https://phabricator.wikimedia.org/T253764]
* Later this week, people who are editing a page-section using the mobile visual editor, will notice a new "Edit full page" button. When tapped, you will be able to edit the entire article. This helps when the change you want to make is outside the section you initially opened. [https://phabricator.wikimedia.org/T387175][https://phabricator.wikimedia.org/T409112]
* [[mw:Special:MyLanguage/Readers/Reader Experience|The Reader Experience team]] is inviting editors to assess whether dark mode should still be considered "beta" on their wiki, based on their experience of how well it functions on desktop and mobile. If the feature is deemed mature, editors can update the interface messages in <code dir=ltr>MediaWiki:skin-theme-description</code> and <code dir=ltr>MediaWiki:Vector-night-mode-beta-tag</code> to indicate that dark mode is ready and no longer considered beta.
* The improved [[mw:Wikimedia_Apps/Team/iOS/Activity_Tab|Activity tab]] which displays user-insights is now available to all users of the Wikipedia iOS app (version 7.9.0 and later). Following earlier A/B testing that showed higher account creation among users with access to the feature, it has been rolled out to 100% of users along with some updates. The Activity tab now shows your edited articles in the timeline, offers editing impact insights like contribution counts and article view trends, and customization options to improve in-app experience for users.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, a bug that prevented [[mw:Special:MyLanguage/Extension:DiscussionTools|DiscussionTools]] from working on mobile has now been fixed, restoring full functionality. [https://phabricator.wikimedia.org/T415303]
'''Updates for technical contributors'''
* The [[m:Special:GlobalWatchlist|Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] that makes this possible continues to improve. The latest upgrade is the inclusion of a [[mw:Extension:GlobalWatchlist#hook|new hook]], <code dir=ltr>ext.globalwatchlist.rebuild</code>, which fires after each watchlist rebuild. This allows you to run gadgets and user scripts for the Special page. [https://phabricator.wikimedia.org/T275159]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.17|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/09|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W09"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 février 2026 à 20:03 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30119102 -->
== Actualités techniques n° 2026-10 ==
<section begin="technews-2026-W10"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/10|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Le [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments|mode Anniversaire]] Wikipedia 25 est maintenant disponible sur Wikipédia en français, anglais, betawi, breton, chinois, espagnol, gorontalo, indonésien, italien, luxembourgeois, madurais, néerlandais, sicilien, tchèque, thaï et vietnamien ! Cette campagne à temps limitée célèbre 25 ans de Wikipédia avec une mascotte : « Baby Globe », disponible sous la forme d'un réglage. Lorsque ce réglage est activé, Baby Globe est montrée sur [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments/article configuration|environ 2 500 articles]], attendant d'être découverte par des lecteurs. Chaque communauté peut choisir d'activer le mode Anniversaire par consensus et en demandant à un administrateur de le rendre disponible et de le personaliser via une [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments#Community Configuration Demo|configuration]] sur le wiki local.
'''Actualités pour la contribution'''
* Le [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-référencement]], une nouvelle fonctionalité pour réutiliser des références avec des détails différents est maintenant disponible sur Wikipédia en suédois, polonais et [[:phab:T418209|quelques autres]]. Vous pouvez [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#test|essayer la fonctionalité]] sur ces projets ou sur testwiki et [https://en.wikipedia.beta.wmcloud.org/wiki/Sub-referencing betawiki]. Les retours des premiers essais sur Wikipédia en allemand ont été [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing/Learnings|publiés dans un rapport]]. Contactez l'équipe de Wikimédia Allemagne si vous êtes [[:m:Talk:WMDE Technical Wishes/Sub-referencing#Pilot wikis|intéressés pour devenir un wiki pilote]].
* La [[mw:Special:MyLanguage/Help:Edit check#Paste check|vérification du collage clavier]] sera disponible sur tous les Wikipédias cette semaine. Cette fonctionalité avertit les nouveaux contributeurs qui collent du texte qu'ils n'ont probablement pas écrit de vérifier si laisser celui-ci risque de causer une violation du droit d'auteur. La vérification du collage clavier [[mw:Special:MyLanguage/Edit check/Tags|marque]] toutes les modifications où l'avertissement a été montré pour permettre leur vérification. Les administrateurs locaux peuvent configurer les différents aspects de cette fonctionalité à travers [[{{#special:EditChecks}}]]. Des [[mw:Special:MyLanguage/Edit check/Paste Check#A/B Experiment|études]] sur 22 wikis ont montré que cette vérification permet une réduction de 18% des annulations comparé au groupe de contrôle. Les traducteurs peuvent [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-visualeditor-ve-mw-editcheck&filter=&optional=1&action=translate aider à traduire] cette fonctionalité.
* <span lang="en" dir="ltr" class="mw-content-ltr">The [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience team]] will be standardizing the user menu in the top right for all mobile users so that it is closer to the desktop experience. Currently this user menu is only visible to users with Advanced Mobile Controls (AMC) turned on. The only change is that a couple buttons previously in the left-side menu will move to the top right for users who do not have AMC turned on. This change is expected to go out March 9 and seeks to improve the user interface.</span> [https://phabricator.wikimedia.org/T413912]
* À partir de la semaine du 2 mars, les emails envoyés lorsqu'une adresse email a été ajoutée, supprimée ou changée pour un compte changera pour adopter un formattage HTML beaucoup plus agréable et plus clair que le texte brut précédent. [https://phabricator.wikimedia.org/T410807]
* Les notifications sont actuellement limitées à 2 000 entrées historiques par utilisateur et remontent à 2013 lorsque la fonctionnalité a été publiée. Le système va être modifié pour ne stocker que les notifications des 5 dernières années, mais jusqu'à 10 000 d'entre elles. Cela contribuera à la santé à long terme des infrastructures et à empêcher que les notifications plus récentes disparaissent trop tôt. [https://phabricator.wikimedia.org/T383948]
* <span lang="en" dir="ltr" class="mw-content-ltr">The [[m:Special:GlobalWatchlist|Global Watchlist]] which lets you view your watchlists from multiple wikis on a single page continues to see improvements. The latest update improves label usage experience. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] now allows activating the [[mw:Special:MyLanguage/Manual:Language#Fallback languages|language fallback system]] for Wikidata items without labels in the viewed language, and showing those labels in the user’s preferred Wikidata language if no <code dir=ltr>uselang=</code> URL parameter is provided.</span> [https://phabricator.wikimedia.org/T373686][https://phabricator.wikimedia.org/T416111]
* L'équipe Wikipédia Android a commencé un test beta de la [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|recherche hybride]] sur Wikipédia en grec. Cette recherche hybride supporte les requêtes sémantique et par mot clés, permettant aux utilisateurs de trouver ce qu'ils cherchent plus facilement.
* Pour des raisons de sécurité, les membres de certains groupes sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir la double authentification]] (A2F) d'activée. Actuellement, l'A2F n'est nécessaire que pour utiliser les droits du groupe, et non pour en faire partie. Vu que ce système admet certaines failles, il sera [[phab:T418580|changé graduellement en mars]]. Les membres de ces groupes ne pourront plus désactiver la dernière méthose d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Il sera toujours possible de rajouter d'autres méthodes d'authentification et d'en enlever, tant qu'une est toujours activée. Dans la seconde moitié de mars, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique aux administrateurs CentralNotice, aux vérificateurs d'utilisateurs, aux administrateurs d'interface, aux masqueurs, aux staff de Wikidata et Wikifonctions ainsi qu'aux bureaux IT et Confiance et sécurité de la WMF. Rien ne changera pour les autres utilisateurs. Voir la tâche liée pour le calendrier de déploiement. [https://phabricator.wikimedia.org/T418580]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, le problème empêchant les utilisateurs de créer une instance dans [https://www.wikibase.cloud/ Wikibase.cloud] a maintenant été résolu. [https://phabricator.wikimedia.org/T416807]
'''Actualités pour la contribution technique'''
* <span lang="en" dir="ltr" class="mw-content-ltr">To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], over the next month the Wikimedia Foundation will implement global API rate limits across our APIs. In early March, stricter limits will be applied to unidentified requests from outside Toolforge/WMCS and API requests that are made from web browsers. In April, higher limits will be applied to identified traffic. These limits are intentionally set as high as possible to minimise impact on the community. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]].</span>
* <span lang="en" dir="ltr" class="mw-content-ltr">The Wikidata Query Service Linked Data Fragment (LDF) endpoint will be decommissioned in February. This endpoint served limited traffic, which was successfully migrated to other data access methods that were better suited to support existing use cases. The hardware used to support the LDF endpoint will be reallocated to support the ongoing backend migration efforts.</span> [https://phabricator.wikimedia.org/T415696]
* Le nouvel analyseur syntaxique Parsoid [[mw:Special:MyLanguage/Parsoid/Parser Unification/Updates|continue d'être déployés sur plus de wikis]], améliorant la pérennité de la platforme et rendant plus facile l'ajout de nouvelles fonctionalités de lecture et de modification. Parsoid est maintenant l'analyseur par défaut sur 488 wikis de la WMF (268 Wikipédias), couvrant plus de 10% de toutes les lectures de pages Wikipédia.
* Le processus et les critères pour [[Special:MyLanguage/Wikimedia Enterprise#Access|demander un accès exceptionnel]] au flux à fort volume de l'API ''Wikimédia Entreprise'' (sans coût pour des utilisations en rapport à notre mission) [[m:Talk:Wikimedia Enterprise#Exceptional access criteria|ont maintenant été publiés]]. Notre but est de donner une documentation plus claire et plus complète aux utilisateurs.
* [https://techblog.wikimedia.org/ Le blog Tech], dédié à la communité technique de Wikimédia [https://techblog.wikimedia.org/2026/02/24/a-tech-blog-diff/ va migrer] vers [[diffblog:|Diff]], le blog pour les nouvelles et événements de la communauté. La migration devrait être terminée en Avril 2026, après quoi les nouveaux posts seront acceptés pour être publiés. Les lecteurs pourront lire les posts - anciens ou nouveaux - sur https://diff.wikimedia.org/.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.18|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/10|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W10"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 mars 2026 à 18:51 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30137798 -->
== <span lang="en" dir="ltr">Tech News: 2026-11</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W11"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/11|Translations]] are available.
'''Weekly highlight'''
* [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies.
* Last week, all wikis had 2 hours of read-only time, and extended unavailability for user-scripts and gadgets. This was due to a security incident which has since been resolved. Work is ongoing to prevent re-occurrences. For current information please see the [[m:Steward's noticeboard#Statement on Meta about today's user script security incident|post on the Stewards' noticeboard]] ([[m:Special:MyLanguage/Wikimedia Foundation/Product and Technology/Product Safety and Integrity/March 2026 User Script Incident|translations]]).
'''Updates for editors'''
* Users facing multiple blocks on mobile will now see the reasons for each block separately, instead of a generic message. This helps them understand why they are blocked and what steps they can take to resolve the issue. For example, users affected for using common VPNs (such as [[Special:MyLanguage/Apple iCloud Private Relay|iCloud Private Relay]]) will receive clearer guidance on what they need to do to start editing again. [https://phabricator.wikimedia.org/T357118]
* Later this week, [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Suggestion Mode]] will become available as a beta feature within the visual editor at all Wikipedias. This feature proactively suggests various types of actions that people can consider taking to improve Wikipedia articles, and learn about related guidelines. The feature is locally configurable, and can also be locally expanded with custom Suggestions. Current settings can be seen at [[Special:EditChecks]] and there are [[mw:Special:MyLanguage/Help:Suggestion mode#For administrators %E2%80%93 local customization|instructions for how administrators can customize]] the links to point to local guidelines. The feature is connected to [[mw:Special:MyLanguage/Help:Edit check|Edit check]] which suggests improvements while someone is writing new content. In the future, the Editing team plans to evaluate the feature's impact with newcomers through a controlled experiment. [https://phabricator.wikimedia.org/T404600]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:23}} community-submitted {{PLURAL:23|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where the cursor became misaligned during the use of CodeMirror’s syntax highlighting, which makes wikitext and code easier to read, has now been fixed. This problem specifically affected users who defined a font rule in a custom stylesheet while creating a new topic with DiscussionTools. [https://phabricator.wikimedia.org/T418793]
'''Updates for technical contributors'''
* API rate limiting update: To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], global API rate limits will be applied this week to requests without a compliant User-Agent that originate from outside Toolforge/WMCS and to unauthenticated requests made from web browsers. Higher limits will be applied to identified traffic in April. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]].
* The new GraphQL API has been released. The API was developed as a flexible alternative to select features of the Wikidata Query Service (WDQS), to improve developer experience and foster adaptability, and efficient data access. Try it out and [[d:Wikidata:Wikibase GraphQL#Feedback and development|give feedback]]. You can also [https://greatquestion.co/wikimediadeutschland/GraphQLAPI/apply sign up for usability tests].
* The [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group|PTAC Unsupported Tools Working Group]] continued improvements to [[commons:Special:MyLanguage/Commons:Video2commons#|Video2Commons]] in February, with fixes addressing authentication errors, large-file handling, task queue visibility, and clearer upload behavior. Work is still ongoing in some areas, including changes related to deprecated server-side uploads. Read [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group#February 2026|this update]] to learn more.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.19|MediaWiki]]
'''In depth'''
* The Article Guidance team invites experienced Wikipedia editors from selected [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators#Collaborators|pilot wikis]] and interested contributors from other Wikipedias to fill out this questionnaire which is available in [https://docs.google.com/forms/d/e/1FAIpQLSfmLeVWnxmsCbPoI_UF2jyRcn73WRGWCVPHzerXb4Cz97X_Ag/viewform English], [https://docs.google.com/forms/d/e/1FAIpQLSd6rzr4XXQw8r4024fE3geTPFe13M_6w7Mitj-YJi0sOlWTAw/viewform?usp=header Arabic], [https://docs.google.com/forms/d/e/1FAIpQLSdok3-RfB18lcugYTUMGkpwmqG_8p760Wv4dCXitOXOszjUDw/viewform?usp=header Bengali], [https://docs.google.com/forms/d/e/1FAIpQLSfjTfYp4jEo0akA4B1e-Nfg3QZPCudUjhJzHzzDi6AHyAaMGA/viewform?usp=header Japanese], [https://docs.google.com/forms/d/e/1FAIpQLScteVoI29Aue4xc72dekk-6RYtvmMgQxzMI900UOawrFrSTWg/viewform?usp=header Portuguese], [https://docs.google.com/forms/d/e/1FAIpQLSetdxnYwL3ub2vqA7awCg5hJZPMIYcDPaiTe12rY9h0GYnVlw/viewform?usp=header Persian], and [https://docs.google.com/forms/d/e/1FAIpQLScNvfJF-Ot-4pzA4qAN771_0QDJ4Li19YcUsaTgSKW8Nc7U_Q/viewform?usp=header Turkish]. Your answers will help the team customize guidance for less experienced editors and help them learn community policies and practices while creating an article. Learn more [[mw:Special:MyLanguage/Article guidance|on the project page]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/11|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W11"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 9 mars 2026 à 19:52 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30213008 -->
== <span lang="en" dir="ltr">Tech News: 2026-12</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W12"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/12|Translations]] are available.
'''Updates for editors'''
* The [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature, also known as [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], has been used for wikitext syntax highlighting since November 2024. It will be promoted out of beta by May 2026 in order to bring improvements and new [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Features|features]] to all editors who use the standard syntax highlighter. If you have any questions or concerns about promoting the feature out of beta, [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|please share]]. [https://phabricator.wikimedia.org/T259059]
* Some changes to local user groups are performed by stewards on Meta-Wiki and logged there only. Now, interwiki rights changes will be logged both on Meta-Wiki and the wiki of the target user to make it easier to access a full record of user's rights changes on a local wiki. Past log entries for such changes will be backfilled in the coming weeks. [https://phabricator.wikimedia.org/T6055]
* On wikis using [[m:Special:MyLanguage/Flagged Revisions|Flagged Revisions]], the number of pending changes shown on [[{{#Special:PendingChanges}}]] previously counted pages which were no longer pending review, because they have been removed from the system without being reviewed, e.g. due to being deleted, moved to a different namespace, or due to wiki configuration changes. The count will be correct now. On some wikis the number shown will be much smaller than before. There should be no change to the list of pages itself. [https://phabricator.wikimedia.org/T413016]
* Wikifunctions composition language has been rewritten, resulting in a new version of the language. This change aims to increase service stability by reducing the orchestrator's memory consumption. This rewrite also enables substantial latency reduction, code simplification, and better abstractions, which will open the door to later feature additions. Read more about [[f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-11|the changes]].
* Users can now sort search results alphabetically by page title. The update gives an additional option to finding pages more easily and quickly. Previously, results could be sorted by Edit date, Creation date, or Relevance. To use the new option, open 'Advanced Search' on the search results page and select 'Alphabetically' under 'Sorting Order'. [https://phabricator.wikimedia.org/T403775]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented UploadWizard on Wikimedia Commons from importing files from Flickr has now been fixed. [https://phabricator.wikimedia.org/T419263]
'''Updates for technical contributors'''
* A new special page, [[{{#special:LintTemplateErrors}}]], has been created to list transcluded pages that are flagged as containing lint errors to help users discover them easily. The list is sorted by the number of transclusions with errors. For example: [[{{#special:LintTemplateErrors}}/night-mode-unaware-background-color]]. [https://phabricator.wikimedia.org/T170874]
* Users of the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature have been using [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] for syntax highlighting when editing JavaScript, CSS, JSON, Vue and Lua content pages, for some time now. Along with promoting CodeMirror 6 out of beta, the plan is to replace CodeEditor as the standard editor for these content models by May 2026. [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|Feedback or concerns are welcome]]. [https://phabricator.wikimedia.org/T419332]
* The [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] JavaScript modules will soon be upgraded to CodeMirror 6. Leading up to the upgrade, loading the <code dir=ltr>ext.CodeMirror</code> or <code dir=ltr>ext.CodeMirror.lib</code> modules from gadgets and user scripts was deprecated in July 2025. The use of the <code dir=ltr>ext.CodeMirror.switch</code> hook was also deprecated in March 2025. Contributors can now make their scripts or gadgets compatible with CodeMirror 6. See the [[mw:Special:MyLanguage/Extension:CodeMirror#Gadgets and user scripts|migration guide]] for more information. [https://phabricator.wikimedia.org/T373720]
* The MediaWiki Interfaces team is expanding coverage of REST API module definitions to include [[mw:Special:MyLanguage/API:REST API/Extensions|extension APIs]]. REST API modules are groups of related endpoints that can be independently managed and versioned. Modules now exist for [https://phabricator.wikimedia.org/T414470 GrowthExperiments] and [https://phabricator.wikimedia.org/T419053 Wikifunctions] APIs. As we migrate extension APIs to this structure, documentation will move out of the main MediaWiki OpenAPI spec and REST Sandbox view, and will instead be accessible via module-specific options in the dropdown on the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox] (i.e., [[{{#Special:RestSandbox}}]], available on all wiki projects).
* The [[mw:Special:MyLanguage/Extension:Scribunto|Scribunto]] extension provides different pieces of information about the wiki where the module is being used via the [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual|mw.site]] library. Starting last week, the library also provides a [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#mw.site.wikiId|way]] of accessing the [[mw:Special:MyLanguage/Manual:Wiki ID|wiki ID]] that can be used to facilitate cross-wiki module maintenance. [https://phabricator.wikimedia.org/T146616]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.20|MediaWiki]]
'''In depth'''
* The [[m:Special:MyLanguage/Coolest Tool Award|2026 Coolest Tool Award]] celebrating outstanding community tools, is now open for nominations! Nominate your favorite tool using the [https://wikimediafoundation.limesurvey.net/435684?lang=en nomination survey] form by 23 March 2026. For more information on privacy and data handling, please see the [[foundation:Special:MyLanguage/Legal:Coolest_Tool_Award_2026_Survey_Privacy_Statement|survey privacy statement]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/12|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W12"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 mars 2026 à 20:35 (CET)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30260505 -->
== <span lang="en" dir="ltr">Upcoming deployment of CampaignEvents extension to Wikibooks</span> ==
<div lang="en" dir="ltr">
<section begin="message"/>
Hello everyone,
We are writing to inform you that the [[mw:Help:Extension:CampaignEvents|CampaignEvents extension]] will be deployed to all Wikibooks projects during the week of '''23 March 2026'''.
This follows last year’s broader rollout across Wikimedia projects. We realized that Wikibooks was not included at the time, and we’re now addressing that to ensure consistency across all communities.
The CampaignEvents extension provides tools to support event and campaign organization on-wiki, including features like on-wiki event registration and collaboration lists(global event list).
We welcome any questions, feedback, or concerns you may have. We are also happy to support anyone interested in trying out the tools.
''Apologies if this message is not in your preferred language. If you’re able to help translate it for your community, please feel free to do so.''
<section end="message"/>
</div>
<bdi lang="en" dir="ltr">[[User:Udehb-WMF|Udehb-WMF]] ([[User talk:Udehb-WMF|discussion]]) 19 mars 2026 à 19:22 (CET)</bdi>
<!-- Message envoyé par User:Udehb-WMF@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Udehb-WMF/sandbox/MM_target&oldid=30284073 -->
== <span lang="en" dir="ltr">Tech News: 2026-13</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W13"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/13|Translations]] are available.
'''Weekly highlight'''
* Wikimedia site users can now log in without a password using passkeys. This is a secure method supported by fingerprint, facial recognition, or PIN. With this change, all users who opt for passwordless login will find it easier, faster, and more secure to log in to their accounts using any device. The new passkey login option currently appears as an autofill suggestion in the username field. An additional [[phab:T417120|"Log in with passkey" button]] will soon be available for users who have already registered a passkey. This update will improve security and user experience. The [[c:File:Passwordless_login_screencast.webm|screen recording]] demonstrates the passwordless login process step by step.
* [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies.
'''Updates for editors'''
* Wikimedia site users can now export their notifications older than 5 years using a [[toolforge:echo-chamber|new Toolforge tool]]. This will ensure that users retain their important notifications and avoid them being lost based on the planned change to delete notifications older than 5 years, as previously announced. [https://phabricator.wikimedia.org/T383948]
* Wikipedia editors in Indonesian, Thai, Turkish, and Simple English now have access to Special:PersonalDashboard. This is an [[mw:Special:MyLanguage/Moderator Tools/Dashboard|early version of an experience]] that introduces newer editors to patrolling workflows, making it easier for them to move from making edits to participating in more advanced moderation work on their project. [https://phabricator.wikimedia.org/T402647]
* The [[Special:Block]] now has two minor interface changes. Administrators can now easily perform indefinite blocks through a dedicated radio button in the expiry section. Also, choosing an indefinite expiry provides a different set of common reasons to select from, which can be changed at: [[MediaWiki:Ipbreason-indef-dropdown]]. [https://phabricator.wikimedia.org/T401823]
* Mobile editors [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#Logged-out|at several wikis]] can now see an improved logged-out edit warning, thanks to the recent updates from the Growth team. These changes released last week are part of ongoing efforts and tests to enhance [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|account creation experience on mobile]] and then increase participation. [https://phabricator.wikimedia.org/T408484]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:36}} community-submitted {{PLURAL:36|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented mobile web users from seeing the block information when affected by multiple blocks has been fixed. They can now see messages of all the blocks currently affecting them when they access Wikipedia.
'''Updates for technical contributors'''
* Images built using Toolforge will soon get the upgraded buildpacks version, bringing support for newer language versions and other upstream improvements and fixes. If you use Toolforge Build Service, review the recent [https://lists.wikimedia.org/hyperkitty/list/cloud-announce@lists.wikimedia.org/thread/EMYTA32EV2V5SQ2JIEOD2CL66YFIZEKV/ cloud-announce email] and update your build configuration as necessary to ensure your tools are compatible. [https://wikitech.wikimedia.org/w/index.php?title=Help:Toolforge/Building_container_images&oldid=2392097#Buildpack_environment_upgrade_process][https://phabricator.wikimedia.org/T380127]
* The [https://api.wikimedia.org/wiki/Main_Page API Portal] documentation wiki will shut down in June 2026. API keys created on the API Portal will continue to work normally. api.wikimedia.org endpoints will be deprecated gradually starting in July 2026. Documentation on the API Portal is moving to [[mw:Wikimedia APIs|mediawiki.org]]. Learn more on the [[wikitech:API Portal/Deprecation|project page]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.21|MediaWiki]]
'''In depth'''
* [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] is considering improvements to [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|automatically generated reference names in VisualEditor]]. Please check out the [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names#Proposed solutions|proposed solutions]] and participate in the [[m:Talk:WMDE Technical Wishes/References/VisualEditor automatic reference names#Request for comment|request for comment]].
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/13|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W13"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 mars 2026 à 17:51 (CET)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30268305 -->
== Actualités techniques n° 2026-14 ==
<section begin="technews-2026-W14"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/14|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Le version Beta de [[abstract:|Abstract Wikipedia]], un nouveau projet Wikimédia indépendant du langage, a été lancée la semaine dernière. Ce projet permet aux communautés de construire des articles Wikipédia dans leur langue natale, qui peuvent directement être lus par les autres utilisateurs et utilisatrices dans leur propre langage. Le wiki fonctionne grâce à des instructions de Wikifunctions et au contenu structuré issu de Wikidata. [[:f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-26|En savoir plus]].
'''Actualités pour la contribution'''
* L'équipe Croissance mène un test A/B afin d'évaluer l'effet d'un message plus clair et plus convivial encourageant à la création de comptes sur les wikis. Actuellement, lorsqu'un utilisateur mobile non connecté lance la modification, un message d'avertissement s'affiche, pouvant paraître abrupt et décourageant. Il présente également la modification par compte temporaire comme option par défaut, au lieu d'inciter à la création d'un compte. Le test est mené sur dix Wikipédia, dont les versions en arabe, français, espagnol et allemand. [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#2. Improve logged-out warning message (T415160)|En savoir plus]].
* L'équipe des applications Wikimédia sollicite vos commentaires sur [[mw:Special:MyLanguage/Wikimedia Apps/Team/Future of Editing on the Mobile Apps|comment devrait fonctionner l'édition dans les applications mobiles Wikipédia]]. La discussion porte sur l'amélioration de l'accès aux outils d'édition lorsque les utilisateurs appuient sur « Modifier ». Cette initiative s'inscrit dans un effort plus large visant à offrir aux lecteurs intéressés par la contribution une expérience utilisateur plus intuitive.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:45|la tâche soumise|les {{formatnum:45}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:45||s}} la semaine dernière]]. Par exemple, un problème avec la récupération de citations à partir du site d'archive de journaux [https://www.newspapers.com Newspapers.com], qui ne fonctionnait plus en raison d'un blocage des requêtes [[mw:Special:MyLanguage/Citoid|Citoid]], a maintenant été résolu. [https://phabricator.wikimedia.org/T419903]
'''Actualités pour la contribution technique'''
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.22|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/14|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W14"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 30 mars 2026 à 21:25 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30329462 -->
== Action Required: Update templates/modules for electoral maps (Migrating from P1846 to P14226) ==
Hello everyone,
This is a notice regarding an ongoing data migration on Wikidata that may affect your election-related templates and Lua modules (such as <code>Module:Itemgroup/list</code>).
'''The Change:'''<br />
Currently, many templates pull electoral maps from Wikidata using the property [[:d:Property:P1846|P1846]], combined with the qualifier [[:d:Property:P180|P180]]: [[:d:Q19571328|Q19571328]].
We are migrating this data (across roughly 4,000 items) to a newly created, dedicated property: '''[[:d:Property:P14226|P14226]]'''.
'''What You Need To Do:'''<br />
To ensure your templates and infoboxes do not break or lose their maps, please update your local code to fetch data from [[:d:Property:P14226|P14226]] instead of the old [[:d:Property:P1846|P1846]] + [[:d:Property:P180|P180]] structure. A [[m:Wikidata/Property Migration: P1846 to P14226/List|list of pages]] was generated using Wikimedia Global Search.
'''Deadline:'''<br />
We are temporarily retaining the old data on [[:d:Property:P1846|P1846]] to allow for a smooth transition. However, to complete the data cleanup on Wikidata, the old [[:d:Property:P1846|P1846]] statements will be removed after '''May 1, 2026'''. Please update your modules and templates before this date to prevent any disruption to your wiki's election articles.
Let us know if you have any questions or need assistance with the query logic. Thank you for your help! [[User:ZI Jony|ZI Jony]] using [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 3 avril 2026 à 19:11 (CEST)
<!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29941252 -->
== Actualités techniques n° 2026-15 ==
<section begin="technews-2026-W15"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/15|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L’[[mw:Special:MyLanguage/Help:Extension:CampaignEvents|extension CampaignEvents]] comprend désormais une nouvelle fonctionnalité de définition d’objectifs de groupe, permettant aux organisateurs de définir et de suivre les objectifs de l’événement, tels que le nombre d’articles créés et de contributeurs participants en temps réel. De même, les participants peuvent travailler vers des cibles communes et voir leur impact collectif au fur et à mesure que l’événement se déroule. Cette fonctionnalité est désormais disponible sur tous les wikis Wikimedia. Pour en savoir plus, consultez [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Collaborative contributions#Goal setting|la documentation]].
* [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] La nouvelle fonctionnalité d'[[mw:Special:MyLanguage/Help:Watchlist labels|étiquettes de liste de suivi]] (annoncée dans les [[m:Special:MyLanguage/Tech/News/2026/07|Actualités techniques 2026-07 ]]) est désormais disponible via l'ÉditeurVisuel, l'éditeur de code et l'«étoile de suivi»(ou le lien de suivi, pour les habillages qui n'ont pas d'icône d'étoile). Auparavant, il n'était possible d'attribuer des étiquettes que via [[Special:EditWatchlist|Modifier la liste de suivi]]. Dans ces trois emplacements, il s'agit d'un nouveau champ situé après le champ d'expiration.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. Par exemple, le problème où les pages de discussion sur mobile avec Parsoid sont inutilisables après les en-têtes de section vides, a maintenant été résolu. [https://phabricator.wikimedia.org/T419171]
'''Actualités pour la contribution technique'''
* La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|fonctionnalité de sous-référencement]], qui permet aux contributeurs d'ajouter des détails à une référence existante sans la dupliquer, sera progressivement déployée sur [[phab:T414094|davantage de wikis]] plus tard cette année. Les wikis utilisant le gadget [[mw:Special:MyLanguage/Reference Tooltips|Reference Tooltips]] sont encouragés à mettre à jour leur version (généralement sur [[m:MediaWiki:Gadget-ReferenceTooltips.js|MediaWiki:Gadget-ReferenceTooltips.js]] comme indiqué [https://en.wikipedia.org/w/index.php?diff=1344408362 ici]) pour assurer la compatibilité. D'autres gadgets liés aux références pourraient également être affectés. [https://phabricator.wikimedia.org/T416304]
* Toutes les éditions de Wikinews seront fermées et passeront en mode lecture seule le 4 mai 2026. Le contenu restera accessible, mais aucune nouvelle modification ni aucun nouvel article ne pourra être ajouté. Cette fermeture a été approuvée par le Conseil d'administration de la Fondation Wikimedia à la suite de discussions prolongées. [[m:Wikimedia Foundation Board noticeboard#Board of Trustees Approves Closure of Wikinews|En savoir plus]].
* L'[[:mw:Special:MyLanguage/API:Action API|API d'action]] a proposé plusieurs formats pour les résultats demandés. L'un d'entre eux, <bdi lang="zxx" dir="ltr"><code><nowiki>format=php</nowiki></code></bdi>, sera bientôt supprimé. Veuillez vous assurer que vos scripts ou robots utilisent le [[mw:Special:MyLanguage/API:Data formats#Output|format JSON]]. Cette suppression devrait affecter très peu de scripts et de robots. [https://phabricator.wikimedia.org/T118538]
* La page [[Special:NamespaceInfo|Special:NamespaceInfo]] inclut désormais les alias d'espace de noms. Par exemple «WP» pour l'espace de noms ''Projet'' (''Wikipédia'') sur la Wikipédia en allemand. [https://phabricator.wikimedia.org/T381455]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.23|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/15|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W15"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 avril 2026 à 18:19 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30362761 -->
== <span lang="en" dir="ltr">Tech News: 2026-16</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W16"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/16|Translations]] are available.
'''Weekly highlight'''
* Experienced editors are invited to [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Main_Page test] the [[mw:Special:MyLanguage/Article guidance|Article guidance]] feature, designed to help less-experienced editors create well-structured, policy-compliant Wikipedia articles. Testing instructions are [[mw:Special:MyLanguage/Article guidance/Test feature guide|available]]. Also, after reviewing [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance the outlines], please provide feedback on the [[mw:Talk:Article guidance|project talk page]]. Based on your input, the feature will be refined and transferred to the pilot Wikipedias to translate and adapt. Check out [[c:File:Article Guidance workflow demo - April 2026.webm|the video]] explaining the feature.
'''Updates for editors'''
* On most wikis, all autoconfirmed users can now use [[Special:ChangeContentModel|Special:ChangeContentModel]] page to [[mw:Special:MyLanguage/Help:ChangeContentModel|create new pages with custom content models]], such as mass message lists, making custom page formats more accessible. Check [[Special:ListGroupRights|Special:ListGroupRights]] for the status of your wiki. [https://phabricator.wikimedia.org/T248294]
* The Growth team has launched an [[mw:Special:MyLanguage/Contributors/Account_Creation_Experiments|account creation experiment]] to evaluate whether adding an account creation button to the mobile web header increases new account registrations and encourages more mobile users to contribute to the wikis. The experiment is currently live on Hindi, Indonesian, Bengali, Thai, and Hebrew Wikipedia, and targets 10% of logged-out mobile web users.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where VisualEditor could get stuck loading on Windows devices with animations turned off, has now been fixed. [https://phabricator.wikimedia.org/T382856]
'''Updates for technical contributors'''
* Starting later this week, {{int:group-abusefilter}} who have the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature enabled will have [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] as the editor at [[Special:AbuseFilter|Special:AbuseFilter]]. This is part of the broader effort to make the user experience more consistent across all editors. [https://phabricator.wikimedia.org/T399673][https://phabricator.wikimedia.org/T419332]
* Tools and bots that access the [[mw:Special:MyLanguage/Notifications/API|Notifications API]] (<bdi lang="zxx" dir="ltr"><code><nowiki>action=query&meta=notifications</nowiki></code></bdi>) will need to update their OAuth or BotPassword grants to also include access to private notifications. [https://phabricator.wikimedia.org/T421991]
* Due to a library upgrade, listings on category pages may be displayed out of order starting on Monday, 20th April. A migration script will be run to correct this, and will take hours to days depending on the size of the wiki (up to a week for English Wikipedia). [https://phabricator.wikimedia.org/T422544]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.24|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/16|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W16"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 avril 2026 à 17:19 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30380527 -->
== Actualités techniques n° 2026-17 ==
<section begin="technews-2026-W17"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/17|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Après deux ans de développement, la version [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]], également connue sous le nom de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], sortira de sa phase bêta le mardi 21 avril. Elle offrira une meilleure lisibilité du code et du wikitext, une réduction des fautes de frappe et d'autres [[mw:Special:MyLanguage/Help:Extension:CodeMirror|avantages]] à tous les utilisateurs du surligneur de syntaxe standard. Un grand merci au bénévole [https://phabricator.wikimedia.org/p/Bhsd/ Bhsd] qui a développé de nombreuses nouvelles fonctionnalités, notamment [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Code folding|le repliement de code]], [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Autocompletion|la saisie semi-automatique]] et [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Linting|l'analyse statique du code]]. [https://phabricator.wikimedia.org/T259059]
* Une mise à jour majeure de l'application Wikipédia pour iOS est en cours de déploiement, en restructurant l'interface pour s'harmoniser avec le tout nouveau design visuel "Liquid Glass" d'Apple. [https://apps.apple.com/us/app/wikipedia/id324715238 Télécharger la dernière version] et découvrez les nouveautés.
'''Actualités pour la contribution'''
* [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|Les listes de lecture]] est une fonctionnalité qui permet aux lecteurs d'enregistrer des articles dans une liste pour les lire ultérieurement. Cette fonctionnalité est actuellement en version bêta sur les Wikipédias en arabe, français, indonésien, vietnamien et chinois, et activée par défaut pour tous les nouveaux comptes sur toutes les Wikipédias.
* Une expérimentation visant à étendre [[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews|les aperçus de page au web mobile]] sera lancée la semaine du 20 avril sur les versions arabe, anglaise, française, italienne, polonaise et vietnamienne de Wikipédia. Les aperçus de page sont des fenêtres contextuelles affichant une miniature, un premier paragraphe et un lien bleu permettant d'ouvrir l'article complet, facilitant ainsi la découverte de contenu. Cette fonctionnalité est déjà disponible sur ordinateur et dans les applications. [[m:Special:MyLanguage/List of experiments in Product and Technology#Template|En savoir plus sur cette expérimentation et d'autres]].
* Sur plusieurs wikis, les contributeurs connectés qui n'ont pas [[mw:Special:MyLanguage/Help:Email confirmation|confirmé leur adresse électronique]] peuvent désormais voir une bannière les invitant à le faire. La confirmation de l'adresse électronique permet à un utilisateur de récupérer l'accès à son compte en cas de perte. [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security#Encouraging users to confirm their email addresses|En savoir plus]]. [https://phabricator.wikimedia.org/T421366]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:15|la tâche soumise|les {{formatnum:15}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:15||s}} la semaine dernière]]. Par exemple, un problème qui entraînait des ralentissements lors de la modification de très grandes pages wiki dans l'éditeur wikitext de 2017, des problèmes de chargement, de prévisualisation et de défilement, ainsi que des problèmes de performance lors de la sélection, de la découpe ou du collage de contenu, a maintenant été résolu. [https://phabricator.wikimedia.org/T184857]
'''Actualités pour la contribution technique'''
* Dans le cadre de la promotion de [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror]] à partir d'une fonctionnalité bêta, tous les utilisateurs se serviront de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] au lieu de [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] pour la coloration syntaxique lors de l'édition de pages de contenu JavaScript, CSS, JSON, Vue et Lua. [https://phabricator.wikimedia.org/T419332]
* <span class="mw-translate-fuzzy">Le service <code>mirrors.wikimedia.org</code> pour les utilisateurs de Debian et Ubuntu sera définitivement arrêté le 15 mai. Le matériel du serveur sera remplacé par des solutions plus performantes. Certains utilisateurs devront peut-être migrer vers un autre serveur qui ne devra prendre qu'une minute. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/LJYRIS4WB66HIRCAO4GIDTXCMDVZRBMA/ Vous pouvez en savoir plus].</span> [https://phabricator.wikimedia.org/T416707]
* Les tables <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi> seront supprimées de [[wikitech:Help:Wiki Replicas|wikireplicas]]. Si vos outils ou requêtes accèdent directement à <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> ou <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi>, veuillez les mettre à jour pour utiliser les tables <bdi lang="zxx" dir="ltr"><code><nowiki>file</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>filerevision</nowiki></code></bdi> avant le 28 mai. [https://phabricator.wikimedia.org/T28741]
* Suite à la récente mise en place de limites de débit globales pour les API non identifiées, la Fondation Wikimedia poursuit ses efforts pour garantir [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|une utilisation équitable de l'infrastructure]] en appliquant des limites globales au trafic des API identifiées à partir de la dernière semaine d'avril. Ces limites sont volontairement fixées au niveau le plus élevé possible afin de minimiser l'impact sur la communauté. Les bots exécutés dans Toolforge/WMCS ou disposant des droits d'utilisateur de bot sur un wiki ne devraient pas être affectés pour le moment. Toutefois, il est conseillé à tous les développeurs de suivre les bonnes pratiques mises à jour. Pour plus d'informations, consultez la page [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|API Wikimedia/Limites de débit]] et la [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits/FAQ|Foire aux questions]].
* L'[[mw:Special:MyLanguage/Attribution API|API d'attribution]] est désormais disponible en [[mw:Special:MyLanguage/Wikimedia APIs/Stability policy|version bêta]]. Elle récupère les informations nécessaires pour créditer les articles et les fichiers multimédias de Wikimedia, quel que soit leur lieu d'utilisation. La documentation de référence est disponible sur la page dédiée au Sandbox REST, accessible sur tous les wikis Wikimedia (comme [https://en.wikipedia.org/w/index.php?api=attribution.v0-beta&title=Special%3ARestSandbox le sandbox REST de Wikipédia en anglais]). N'hésitez pas à partager vos commentaires sur la [[mw:Talk:Attribution API|page de discussion du projet]].
* Il n'y aura pas de nouvelle version de MediaWiki cette semaine.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/17|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W17"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20 avril 2026 à 17:00 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30432763 -->
== Request for comment (global AI policy) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies for writing in English. {{int:Please-translate}}
A [[:m:Requests for comment/Artificial intelligence policy|request for comment]] is currently being held to decide on a global AI policy. {{int:Feedback-thanks-title}}
[[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 26 avril 2026 à 02:57 (CEST)
</bdi>
<!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30424282 -->
== Actualités techniques n° 2026-18 ==
<section begin="technews-2026-W18"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/18|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Un changement dans la manière dont les utilisateurs et utilisatrices sont automatiquement confirmés est en cours pour améliorer la protection contre le vandalisme. Actuellement, il suffit d’avoir un compte depuis quelques jours avec quelques contributions pour être ajouté au groupe [[{{int:grouppage-autoconfirmed/{{CONTENTLANGUAGE}}}}|{{int:group-autoconfirmed}}]]. Cette configuration tend à être exploitée par certains vandales qui créent des comptes et commencent à les utiliser après un certain temps. Pour réduire ce problème, la configuration va changer la semaine prochaine afin que l’âge du compte minimum pour être confirmé automatiquement ne soit calculé qu’à partir de la première modification, au lieu de la date d’inscription. L’âge minimum du compte restera le même, c’est seulement le point de départ pour calculer cet âge qui change. Ce changement ne sera déployé que sur les wikis qui nécessitent au moins une contribution pour satisfaire les conditions de confirmation automatique. [https://phabricator.wikimedia.org/T418484]
* Tous les utilisateurs et utilisatrices de Wikipédia avec un nouveau compte et ceux qui ont activé l’option « activer automatiquement la plupart des fonctionnalités bêta » peuvent désormais utiliser la fonctionnalité bêta de [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|listes de lecture]] pour enregistrer des articles à lire plus tard. Cela permet d’organiser les lectures qui nous intéressent à un endroit unique pour y accéder facilement.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, le problème avec les images d’infoboite qui avaient une marge intérieure immense dans Firefox a été corrigé. [https://phabricator.wikimedia.org/T423676]
'''Actualités pour la contribution technique'''
* Pour rappel, la limite globale d’accès à l’API sera appliquée cette semaine pour identifier le trafic de l’API. Le but est d’aider à garantir un [[mw:MediaWiki Product Insights/Responsible Reuse|accès équitable à l’infrastructure]]. Les robots qui s’exécutent dans Toolforge ou WMCS, ou avec le droit utilisateur ''robot'' sur les wikis, ne devraient pas être affectés pour le moment. Cependant, il est conseillé à tous les développeurs et développeuses de se conformer aux nouvelles bonnes pratiques à suivre. Pour plus d’informations, notamment la limite globale d’accès effective, consultez [[mw:Wikimedia APIs/Rate limits|la page sur la limite d’accès des API de Wikimedia]] et les [[mw:Wikimedia APIs/Rate limits/FAQ|questions-réponses]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.26|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/18|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W18"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 avril 2026 à 20:06 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30458046 -->
== Actualités techniques n° 2026-19 ==
<section begin="technews-2026-W19"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/19|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L’équipe chargée des fonctionnalités de [[mw:Special:MyLanguage/Article guidance|Guidage des articles]] invite les contributeurs et contributrices expérimentés des [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators|Wikipédia pilotes]] (arabe, bangla, japonais, portugais, persan, turc, anglais simplifié, espagnol et français) à contribuer à la traduction et à l’adaptation des [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance exemples de trames d’articles]. Ces trames guideront les contributeurs dans la création d’articles clairs, bien structurés et conformes aux règles lors de l’utilisation de [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Special:NewArticle la fonctionnalité] dès son lancement en mai 2026. Des [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|instructions simples]] expliquant comment traduire et adapter ces trames sont disponibles.
'''Actualités pour la contribution'''
* Le [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif sur les produits et les technologies]] a publié [[:m:Special:MyLanguage/Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|une proposition de recommandation]] d’une procédure type que les organisations affiliées à Wikimedia pourraient suivre pour contribuer au domaine technique. Les membres de la communauté sont invités à donner leur avis sur cette recommandation avant le 8 mai [[:m:Talk:Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|sur la page de discussion]].
* Le nombre de préférences de taille de la miniature disponibles dans MediaWiki va être réduit à trois options standardisées : ''petite'' (180 px), ''moyenne'' (250 px) et ''large'' (400 px), dans le cadre du travail en cours pour améliorer les performances et réduire la pression sur les services de miniatures. Par conséquent, les préférences existantes seront automatiquement adaptées à la nouvelle taille la plus proche (par exemple, les petites tailles comme 120 px ou 150 px s’afficheront à 180 px, tandis que les grandes tailles comme 300 px ou 360 px s’afficheront à 400 px). L’interface des préférences sera bientôt mise à jour pour refléter ces changements, et les utilisateurs qui souhaitent s’y opposer ou donner leur avis peuvent le faire. [https://phabricator.wikimedia.org/T424909]
* Dorénavant, même lorsqu’une permission expire automatiquement, les utilisateurs recevront une notification Echo similaire à la notification normale pour les changements de permissions. Quant au [[m:Special:MyLanguage/Global reminder bot|robot global de rappel]], il continue de prévenir les utilisateurs une semaine ''avant'' que leurs droits ne soient sur le point d’expirer, afin qu’ils puissent les faire renouveler.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]]. Par exemple, le problème du sélecteur de langue ULS dans [[m:Special:Translate|Special:Translate]] qui faisait défiler verticalement alors qu’il ne devait pas, a été résolu. Auparavant, lorsque les utilisateurs ouvraient le menu déroulant « Traduire en français » et commençaient à saisir le nom d’une langue, la boîte de dialogue défilait verticalement de quelques pixels même lorsqu'il y avait suffisamment d’espace pour afficher tous les résultats. Le menu déroulant ne se déplace plus inutilement lors du filtrage des langues. [https://phabricator.wikimedia.org/T358864]
* La [[m:Special:GlobalWatchlist|liste de suivi globale]], qui vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page, continue de s’améliorer. Par exemple, les listes de suivi pour les sites avec Wikibase tels que [[:d:|Wikidata]] prennent désormais en charge les éléments [[mw:Special:MyLanguage/Extension:EntitySchema|EntitySchema]] pour un meilleur suivi. Le mode Mises à jour en direct actualise désormais la page spéciale toutes les 60 secondes afin de se conformer aux [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|nouvelles limites globales d’accès à l’API]] pour une meilleure réactivité en temps réel. Par ailleurs, un bug de directionnalité du texte qui affichait les liens comme « changements 3 » au lieu de « 3 changements » dans les listes à directions mixtes a été corrigé. [https://phabricator.wikimedia.org/T415450][https://phabricator.wikimedia.org/T424422][https://phabricator.wikimedia.org/T418091]
'''Actualités pour la contribution technique'''
* La deuxième phase de [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|limitations globales d’accès à l’API]] a été déployée pour réduire l’[[diffblog:2026/03/26/quo-vadis-crawlers-progress-and-whats-next-on-safeguarding-our-infrastructure/|impact des robots IA]] et assurer un accès équitable et durable aux ressources de Wikimedia, en donnant la priorité au trafic humain et conforme à notre mission. Les [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits#Limits|limites]] ne s’appliquent plus par heure mais par minute, produisant une meilleure répartition dans les structures de trafic ainsi qu’une meilleure prévisibilité de la charge de l’API. Les utilisateurs de la communauté ne devraient pas être affectés, et aucune action n’est requise. Les premières indications montrent que certains requérants basés sur l'agent utilisateur ajustent leur comportement, et environ 64 % du trafic API automatisé a été identifié. La surveillance continue, et Wikimedia Enterprise reste disponible pour l’assistance commerciale.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.27|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/19|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W19"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 4 mai 2026 à 22:43 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30498077 -->
== Actualités techniques n° 2026-20 ==
<section begin="technews-2026-W20"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/20|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* La Communauté Technique a publié [[m:Special:MyLanguage/Community Wishlist/How to write a good wish|de nouvelles directives]] expliquant comment les souhaits sur la Liste de souhaits de la communauté sont triés et priorisés. La documentation vise à aider les contributeurs à rédiger des propositions plus solides en clarifiant les facteurs qui influencent les décisions de priorisation. Au-delà du nombre de votes, les directives mettent en avant des considérations telles que l'impact potentiel sur la communauté pour déterminer quels souhaits avanceront.
'''Actualités pour la contribution'''
* L'équipe de croissance des lecteurs lance une expérience pour tester une nouvelle [[mw:Special:MyLanguage/Readers/Reader_Growth/Share_Card|fonctionnalité de Partage de Carte]] qui permet aux lecteurs de créer des cartes visuellement attrayantes à partir d'articles Wikipédia ou de sections d'articles sélectionnées et de les partager en ligne, chaque carte renvoyant à l'article original afin d'aider à augmenter le lectorat et la découverte des articles. Le test A/B réservé aux mobiles ne sera disponible qu'à une partie des lecteurs sur les Wikipédia en arabe, chinois, français, vietnamien et anglais afin de mieux comprendre les habitudes de lecture et de partage, et est prévu pour commencer la semaine du 18 mai pour une durée de quatre semaines.
* Les applications Wikipedia pour Android et iOS ont récemment publié en version bêta le [[mw:Special:MyLanguage/Wikimedia_Apps/Team/25th_Birthday_Reading_Challenge|défi de lecture de 25 jours]], dans le cadre des efforts visant à stimuler l'engagement des lecteurs en encourageant les utilisateurs à atteindre des objectifs de lecture. Pour suivre leur série de lectures pendant le défi, les utilisateurs de l'application peuvent ajouter un widget avec Baby Globe à leur écran d'accueil. Le défi commence officiellement le 11 mai.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:17|la tâche soumise|les {{formatnum:17}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:17||s}} la semaine dernière]]. Par exemple, un problème où la préférence globale pour activer la coloration syntaxique dans le wikitexte pouvait s'éteindre de manière inattendue après avoir été activée a maintenant été corrigé. [https://phabricator.wikimedia.org/T425286]
'''Actualités pour la contribution technique'''
* [[File:Octicons-tools.svg|12px|link=|alt=|Sujet technique]] Le module ResourceLoader <bdi lang="zxx" dir="ltr"><code><nowiki>mediawiki.ui.input</nowiki></code></bdi>, obsolète depuis [[m:Special:MyLanguage/Tech/News/2023/39|septembre 2023]], sera supprimé cette semaine. Il existe un [[mw:Special:MyLanguage/Codex/Migrating_from_MediaWiki_UI|guide pour migrer de l’interface MediaWiki UI vers Codex]] pour tous les outils qui l’utilisent. [https://phabricator.wikimedia.org/T420125]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.2|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/20|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W20"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11 mai 2026 à 21:20 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30524429 -->
== Actualités techniques n° 2026-21 ==
<section begin="technews-2026-W21"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/21|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L'équipe de Wikipédia abstraite a identifié cinq wikis pilotes potentiels pour évaluer leur intérêt à adopter des articles abstraits sur leurs wikis. Les pilotes sont Wikipédia en Malayalam, en Bengali, en Dagbani, en Arabe et en Indonésien. La période de retour d'information sera ouverte jusqu'au 22 mai. Si votre communauté est intéressée à devenir un pilote, [[m:Talk:Abstract Wikipedia|faites-nous savoir sur Meta]].
'''Actualités pour la contribution'''
* Une expérience visant à afficher [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|les listes de lecture]] aux lecteurs non connectés sur le web mobile sera lancée le 18 mai sur les Wikipédias Allemande, Espagnole, Italienne, Portugaise, Polonaise, Néerlandaise, Turque et Ourdou, et durera un mois. Cet effort soutient des objectifs plus larges consistant à aider les lecteurs à enregistrer et organiser des articles pour une lecture ultérieure, tout en encourageant des habitudes qui pourraient mener à de futures contributions sur Wikipédia.
* Pour prendre en charge un bouton de marquage dans la fonctionnalité bêta Liste de lecture, le menu "Outils > Action" a été mis à jour pour afficher des icônes, y compris l'indicateur en forme d'étoile de suivi qui aide les éditeurs à identifier les articles suivis temporairement. Les icônes correspondent désormais également à celles utilisées sur mobile, améliorant la cohérence entre les plateformes. Le changement est actuellement limité au menu des actions et concerne principalement les éditeurs ayant des droits d'utilisateur privilégié. [https://phabricator.wikimedia.org/T426008]
* [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Mode de Suggestion]] a été publié en tant qu'[[w:en:A/B test|test A/B]] pour les nouveaux éditeurs sur le site mobile à [[phab:T421189|~15 Wikipédias]]. L'expérience mesurera l'impact que le Mode de Suggestion a sur la proportion de sessions d'édition sur le web mobile par des nouveaux éditeurs qui aboutissent à des modifications constructives (non annulées) des articles. L'expérience évaluera également l'impact de la fonctionnalité sur la rétention des éditeurs et surveillera les changements dans les taux d'annulation et de blocage.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, un problème dans l'application Android de Wikipédia où les images pourraient parfois ne pas se charger après avoir ouvert une notification de liste de lecture recommandée, a maintenant été corrigé. [https://phabricator.wikimedia.org/T418231]
'''Actualités pour la contribution technique'''
* L'[[mw:Special:MyLanguage/Wikidata Platform|équipe de la Plateforme Wikidata]] a publié sa [[d:Special:MyLanguage/Wikidata:SPARQL query service/WDQS backend update/Backend Replacement|recommandation de remplacement du backend]] et l'[[wikitech:Wikidata Query Service/WDQS Architecture re-design|architecture technique]] qui l'accompagne pour la migration du Wikidata Query Service (WDQS) hors de Blazegraph grap. Les retours sont attendus jusqu'au 25 mai 2026, en particulier sur les éventuelles lacunes et impacts sur les cas d'utilisation avancés. Les membres de la communauté Wikidata et les utilisateurs de WDQS sont également encouragés à aider à identifier les outils et flux de travail à fort impact qui pourraient nécessiter une attention sur [[d:Wikidata:SPARQL query service/WDQS backend update/High-Impact Use Cases|cette page]]. Les retours peuvent être partagés sur la [[d:Wikidata talk:SPARQL query service/WDQS backend update|page de discussion de la migration]] ou lors de la [[d:Special:MyLanguage/Wikidata:Blazegraph Migration Office Hours|prochaine heure de bureau]]. Voir le [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|bulletin de l'équipe WDP]] pour plus de détails.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.3|MediaWiki]]
'''En détails'''
* Sur les Wikipédia en anglais, en français, en japonais et quelques autres, il y a eu un [[diffblog:2025/09/02/better-detecting-bots-and-replacing-our-captcha/|essai de hCaptcha]], un service tiers de détection de robots. L'essai a montré que hCaptcha détecte et dissuade efficacement certaines activités automatisées de mauvaise foi, à la fois par lui-même et en donnant des signaux aux [[w:en:Wikipedia:Village pump (technical)/Archive 225#Introducing SuggestedInvestigations|checkusers et stewards]] pour qu'ils enquêtent. Comme les résultats étaient positifs, hCaptcha sera déployé sur toutes les wikis au cours des prochaines semaines. [[mw:Special:MyLanguage/Product Safety and Integrity/Anti-abuse signals/hCaptcha|Voir la page du projet hCaptcha]] pour des informations techniques sur la mise en œuvre et les protections de la vie privée. [[diffblog:2026/05/04/better-detecting-bots-and-replacing-our-captcha-part-2/|En savoir plus]].
* La dernière mise à jour de la Technologie communautaire est désormais disponible, avec des progrès dans plusieurs initiatives de la Liste de souhaits communautaire, y compris l'extension des listes de lecture de l'application mobile au site web, la prise en charge de nouvelles langues pour "Who Wrote That" et le Tableau de bord personnel, des améliorations du rendu 3D et des graphiques, ainsi que des travaux à venir sur le tri des pages de discussion, la lecture audio et les flux de travail d'édition. La mise à jour partage également les priorités actuelles, les tendances de l'état de la Liste de souhaits et les opportunités de retour d'information de la communauté sur les domaines de concentration futurs et le Plan annuel 2026–2027 de la Wikimedia Foundation. [[m:Special:MyLanguage/Community Wishlist/Updates#May 13, 2026: Latest updates from the Community Tech team|Lisez le bulletin d'information complet pour plus de détails]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/21|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W21"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18 mai 2026 à 22:21 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30539262 -->
== Actualités techniques n° 2026-22 ==
<section begin="technews-2026-W22"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/22|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Faisant suite à une [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#LOWM|expérience fructueuse sur la création de comptes]], un message d'avertissement pour les personnes déconnectées sera déployé sur les wikis Wikimédia durant la première semaine de juin. Ce changement n'affectera que les personnes déconnectées sur l'interface web mobile qui commencent à modifier. Cette nouvelle expérience est faite pour encourager la création de comptes, tout en autorisant aux utilisateurs de modifier à l'aide de comptes temporaires. Les résultats de l'expérience ont montré une augmentation de la création de compte d'environ 27 % pour ceux ayant vu le nouveau message. Comme prévu, puisque plus de personnes créent un compte, la création de comptes temporaires a diminué de 16 %. L'expérience n'a pas montré d'autres changements sur la qualité des modifications ou sur les autres indicateurs surveillés. [https://phabricator.wikimedia.org/T424595]
'''Actualités pour la contribution'''
* Pour des raisons de sécurité, les membres de certains groupes d’utilisateurs sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir l'authentification à 2 facteurs]] (A2F) d'activée. Les membres de ces groupes seront dans l'impossibilité de désactiver la dernière méthode d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Ces utilisateurs auront toujours la possibilité d'ajouter ou d'enlever des nouvelles méthodes d'authentification, tant qu'une de ces méthodes est toujours activée. Dans les prochaines semaines, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique entre autres aux bureaucrates. Veuillez lire les tâches liées pour les dates de déploiement. [https://phabricator.wikimedia.org/T423119][https://phabricator.wikimedia.org/T423120]
* L'[[m:Special:MyLanguage/WMDE Technical Wishes|équipe des souhaits techniques de Wikimédia Allemagne (WMDE)]] va lancer un [[w:fr:Test A/B|test A/B]] sur [[:phab:T415904|10 wikis]], pour essayer des [[m:WMDE Technical Wishes/References/Reference Previews|améliorations potentielles pour les aperçus de références]]. Cette expérience durera environ 2 semaines à la fin mai ou début juin et affectera 10 % du lectorat sur ordinateur sur les wikis participants.
* Après deux expériences fructueuses, l'équipe Croissance du lectorat déploiera une fonctionnalité de [[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|visionnage d'images]] en bêta pour toutes les Wikipédia sur mobile le 25 mai. Cela veut dire que toutes les personnes ayant les fonctionnalités bêtas activées verront cette fonctionnalité. Les autres pourront l’activer dans leurs préférences. Cette fonctionnalité inclura un carrousel de toutes les images d'un article en haut de celui-ci, avec la possibilité pour les contributeurs d’[[mw:Readers/Reader_Growth/Image_Browsing#Phase_2.1_beta_feature|exclure des images du carrousel d'un article ou d'enlever la fonctionnalité pour l'entièreté de l'article]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, les fichiers STL tridimensionnels étaient affichés incorrectement par l'extension 3D du lecteur multimédia, ce qui est maintenant corrigé. [https://phabricator.wikimedia.org/T416723]
'''Actualités pour la contribution technique'''
* Les classes CSS dépréciées <bdi lang="zxx" dir="ltr"><code><nowiki>tleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>tright</nowiki></code></bdi> ont été remplacées par <bdi lang="zxx" dir="ltr"><code><nowiki>floatleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>floatright</nowiki></code></bdi> car les premières ne fonctionnent pas correctement sur toutes les plateformes, dont l'interface web mobile et l'application mobile. Les projets se servant de ces classes sont encouragés à vérifier leur usage et à planifier leur migration. Sachez que <bdi lang="zxx" dir="ltr"><code><nowiki>floatleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>floatright</nowiki></code></bdi> pourraient aussi être dépréciées dans le futur, même s’il n'y a pas de calendrier défini. [[phab:T426452|En savoir plus]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.4|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/22|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W22"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 25 mai 2026 à 23:52 (CEST)
<!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30584502 -->
== Votez maintenant aux élections 2026 de l'U4C ==
<section begin="announcement-content" />
Les votants éligibles sont invités à participer à l'élection 2026 du [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Comité de coordination du Code de conduite universel]]. De plus amples informations – notamment sur la vérification de l'éligibilité, le processus de vote, les candidats et un lien vers le scrutin – sont disponibles sur Meta à la [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|page d'informations sur les élections de 2026]]. Le scrutin se termine le 2 juin 2026 à [https://zonestamp.toolforge.org/1780358400 00 h 00 UTC].
Veuillez voter si votre compte est éligble. Les résultats seront disponibles avant le 14 juin 2026. -- en coopération avec l'U4C.<section end="announcement-content" />
[[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 27 mai 2026 à 19:14 (CEST)
<!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== Actualités techniques n° 2026-23 ==
<section begin="technews-2026-W23"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/23|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L'équipe [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience]] mène une expérience pour montrer la fonctionnalité [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|listes de lecture]], qui est encore en développement, aux lecteurs non connectés sur mobile afin de tester si elle encourage la création de compte à un rythme plus élevé que le bouton watchstar. L'[[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists#Experiment timeline|expérience]] a été lancée le 18 mai sur les wikis en allemand, espagnol, italien, portugais, polonais, néerlandais, turc et ourdou, et elle durera un mois.
* L'équipe Wikimedia Apps a publié la [[mw:Special:MyLanguage/Wikimedia Apps/Team/Explore Feed Refresh/Phase 1|Phase 1]] du flux d'accueil repensé pour l'application Android Beta. Le nouveau flux d'accueil comprend un onglet « Communauté » actualisé et un onglet « Pour vous » personnalisé contenant des recommandations de lecture mises à jour quotidiennement. La refonte fait partie d'un effort plus large visant à améliorer la découverte de contenu et à créer des expériences d'apprentissage plus engageantes dans les applications Wikipédia.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:18|la tâche soumise|les {{formatnum:18}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:18||s}} la semaine dernière]]. Par exemple, un problème où les images pouvaient ne pas se charger pour certaines modifications suggérées sur [[w:Special:Homepage|Special:Homepage]], laissant la vignette bloquée dans un état de chargement, a maintenant été corrigé. [https://phabricator.wikimedia.org/T424048]
'''Actualités pour la contribution technique'''
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.5|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/23|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W23"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 1 juin 2026 à 23:08 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30613639 -->
== Actualités techniques n° 2026-24 ==
<section begin="technews-2026-W24"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/24|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Wikimedia Entreprise a relevé les limites d’utilisation gratuite de ses API. La limite mensuelle de requêtes pour l’API « à la demande » (<i lang="en">On-demand</i>) est passée de {{formatnum:5000}} à {{formatnum:50000}} requêtes, tandis que celle de l’API des instantanés (<i lang="en">Snapshot</i>) est passée de 15 à 30 requêtes par mois. De plus, les instantanés de contenus structurés sont désormais accessibles aux comptes gratuits. Ces changements élargissent l’accès aux données de Wikimedia Entreprise pour les développeurs et développeuses, les chercheurs et chercheuses et les organisations qui utilisent les contenus Wikimédia. [https://enterprise.wikimedia.com/blog/enhanced-free-api]
'''Actualités pour la contribution'''
* La [[mw:Special:MyLanguage/Wikimedia_Apps/Team/Explore Feed Refresh/Phase 1|nouvelle version du Fil d’exploration]], désormais appelé « Fil d’accueil », est en cours de déploiement auprès de 50 % des utilisateurs de l’application Wikipédia pour Android. Le fil d’accueil aide le lectorat à découvrir du contenu pertinent grâce à deux nouveaux onglets : « Communauté » et « Pour vous ». L’onglet « Communauté » propose un flux défilant de contenus sélectionnés et d’actualités provenant de l’ensemble de la communauté et du mouvement Wikimédia, tandis que l’onglet « Pour vous » offre une expérience en plein écran et par glissement qui présente des contenus adaptés aux centres d’intérêt de l’utilisateur ou utilisatrice. Cette refonte s’inscrit dans le cadre d’un travail en cours visant à améliorer la découverte et à enrichir l’expérience d’apprentissage au sein de l’application Wikipédia.
* Le jeu-questionnaire quotidien [[mw:Special:MyLanguage/Wikimedia Apps/Team/iOS/"Which came first?" Game|Qu’est-ce qui est arrivé en premier ?]] est désormais disponible dans la version bêta de l’application Wikipédia pour iOS en anglais, allemand, français, portugais, russe, espagnol, arabe, chinois et turc. Le jeu s’appuie sur des événements historiques tirés de la rubrique « Éphéméride » de Wikipédia et met les lecteurs au défi de deviner lequel des deux événements s’est produit en premier. Le jeu avait déjà été lancé sur Android. Les communautés souhaitant rendre le jeu disponible dans leur langue peuvent [[mw:Special:MyLanguage/Wikimedia_Apps/Team/Games#Game availability by language|consulter les instructions et les conditions requises]].
* [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|Les sous-références]], une nouvelle fonctionnalité de MediaWiki permettant aux contributeurs de réutiliser des références avec des détails différents, va commencer à être déployée sur les wikis Wikimédia après une phase pilote réussie. Le déploiement débutera le 8 juin pour la plupart des [[wikitech:Deployments/Train#Wednesday|wikis du groupe 1]] et Wikipédia en français, puis d'autres éditions linguistiques de Wikipédia bénéficieront de cette fonctionnalité au cours des prochains mois. Les communautés sont invitées à se préparer en vérifiant s’il existe des [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-cite&language=en&action_source=search&filter=%21translated&optional=1&action=translate messages non traduits de l’extension Cite] dans leur langue et en passant en revue toute utilisation de l’outil [[mw:Special:MyLanguage/Reference Tooltips|Infobulles des références]], qui pourraient nécessiter des [[:phab:T416304#11668731|mises à jour]] pour prendre en charge la nouvelle fonctionnalité. Les wikis utilisant les [[mw:Special:MyLanguage/Help:Reference Previews|aperçus de référence]] n’ont aucune action à entreprendre. Les communautés peuvent également créer la [[Special:TrackingCategories|catégorie de suivi]] ''cite-tracking-category-ref-details'' en tant que catégorie cachée à l’aide de <code><nowiki>__HIDDENCAT__</nowiki></code> (ou d’un modèle dédié), et la relier à l’élément Wikidata correspondant [[d:Q129764848]]. [https://phabricator.wikimedia.org/T425662]
* L'[[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews#Experimentation|expérience d'Aperçus de page]] sur le Web mobile a pris fin. L'équipe a décidé de ne pas déployer cette fonctionnalité après que les résultats ont montré qu'elle n'avait pas d'impact statistiquement significatif sur la fidélisation des lecteurs, l'amélioration de la fidélisation étant le principal indicateur de réussite. Les « Aperçus de page », déjà disponibles sur ordinateur et dans les applications, affichent une vignette, le premier paragraphe et un lien vers l'article complet lorsque les lecteurs cliquent sur un lien bleu. L'expérience a testé cette fonctionnalité sur le Web mobile sur six versions de Wikipédia.
* La [[mw:Special:MyLanguage/Codex/Design/Icons|bibliothèque d'icônes de l'interface utilisateur]] sera [[phab:T399175|mise à jour dans le courant de cette semaine ou la semaine prochaine]]. La plupart des quelque 300 icônes ont été légèrement peaufinées et une trentaine de nouvelles icônes ont été ajoutées. Ces modifications améliorent les icônes afin de les rendre plus cohérentes et plus compréhensibles, et d'offrir un meilleur équilibre visuel lorsqu'elles sont utilisées en groupe.
* L'interface [[mw:Special:MyLanguage/Universal Language Selector|Sélecteur universel de langue]] (ULS) de MediaWiki, qui aide les utilisateurs à sélectionner du contenu dans d'autres langues, a été mise à jour. La nouvelle version améliore la rapidité et l'accessibilité, et les utilisateurs des projets Wikimédia peuvent désormais épingler des langues pour changer de langue plus rapidement. Le déploiement sur les sites Wikimédia se fera progressivement au cours des prochaines semaines. Vous pouvez la tester dès maintenant en tant que fonctionnalité bêta en sélectionnant [[Special:Preferences#mw-prefsection-betafeatures|les fonctionnalités bêta]] dans les préférences de votre profil et partager vos commentaires sur [[mw:Special:MyLanguage/Universal Language Selector/New ULS|la page du projet]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:21|la tâche soumise|les {{formatnum:21}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:21||s}} la semaine dernière]]. Par exemple, un problème du le tableau de bord d'analyse des pages vues sur pageviews.wmcloud.org qui a arrêté de mettre à jour les données graphiques en mai 2026, affectant tous les utilisateurs, a été résolu. [https://phabricator.wikimedia.org/T427171]
'''Actualités pour la contribution technique'''
* La signature de la fonction <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink()</nowiki></code></bdi> a été simplifiée. Les développeurs peuvent désormais passer un objet de configuration à la place d'une liste de paramètres positionnels lors de la création de liens vers des portlets. L'ancienne signature de la fonction reste prise en charge à des fins de compatibilité ascendante. Par exemple, au lieu de : <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink('p-cactions', '#', 'Stub', 'ca-stubtag', 'Add a stub tag to this page');</nowiki></code></bdi>, utilisez <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink('p-cactions', { href: '#', text: 'Stub', id: 'ca-stubtag', tooltip: 'Add a stub tag to this page' });</nowiki></code></bdi>. Les responsables de la maintenance des scripts sont invités à passer en revue les utilisations existantes de <bdi lang="zxx" dir="ltr"><code><nowiki>addPortletLink()</nowiki></code></bdi> et à les mettre à jour si nécessaire. Cette modification sera disponible sur tous les wikis à partir du 11 juin. Merci à Gerges, bénévole de la communauté, d'avoir apporté cette amélioration. [https://phabricator.wikimedia.org/T427945]
* '''Discussion sur la liste de souhaits de la communauté''': les [[m:Special:MyLanguage/Community Wishlist/Updates#May 20, 2026: Community Tech becomes a program|changements introduits]] par les équipes Produit et Technologie visent à augmenter le nombre et la complexité des souhaits exaucés, notamment par la dissolution de l'équipe Community Tech. Ils [[m:Special:MyLanguage/Community Wishlist/Updates|mènent actuellement des discussions]] sur une [[m:Talk:Community Wishlist#Proposed direction for Wishlist|orientation proposée pour la liste de souhaits]] émanant des membres de la communauté. Cela inclut des moyens de structurer le vote annuel, un meilleur suivi des souhaits, la suppression de certains domaines prioritaires et des [[m:Special:MyLanguage/Community Wishlist/Updates|mises à jour concernant le personnel]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.6|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/24|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W24"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 8 juin 2026 à 23:29 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30650573 -->
== Actualités techniques n° 2026-25 ==
<section begin="technews-2026-W25"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/25|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* L'[[mw:Special:MyLanguage/Readers/Reader Growth|équipe chargée de la croissance du lectorat]] a lancé une fonctionnalité bêta d'[[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|exploration des images]] sur la version mobile de toutes les Wikipédias. Cette fonctionnalité affiche un carrousel d'images en haut des articles contenant au moins trois images. Les contributeurs peuvent configurer cette fonctionnalité à l'aide des commandes suivantes : pour masquer une image spécifique sur une page, utilisez soit <code>class=notpageimage</code> pour l'exclure des aperçus miniatures, soit <code>class=noviewer</code> pour l'exclure de MediaViewer. Le carrousel peut également être désactivé complètement sur une page à l'aide du mot magique <code><nowiki>__NOMEDIAVIEWERCAROUSEL__</nowiki></code>. Pour faire des retours ou signaler des bugs, rendez-vous sur la [[mw:Talk:Readers/Reader Growth/Image Browsing|page de discussion du projet]].
* Les [[mw:Special:MyLanguage/Help:Tables#class="wikitable"|Wikitables]] peuvent désormais être [[mw:Special:MyLanguage/Help:Sortable tables#Forcing the initial sort direction|triées par ordre décroissant]] dès le premier clic en ajoutant <code dir=ltr>data-sort-order="desc"</code> à la cellule d'en-tête. Auparavant, par défaut, cliquer une première fois sur l'en-tête d'une colonne entraînait un tri par ordre croissant. Cette nouveauté offre davantage de contrôle et de flexibilité pour les Wikitables, tandis que le comportement par défaut pour les clics suivants reste inchangé. [https://phabricator.wikimedia.org/T398416]
'''Actualités pour la contribution'''
* La fonctionnalité d'[[mw:Special:MyLanguage/Article guidance|Aide à la rédaction d'articles]] est actuellement en phase de test auprès de certains contributeurs qui créent de nouveaux articles sur les Wikipédias en anglais simplifié, en français et en turc. L'expérience débutera bientôt sur les Wikipédias en arabe et en bengali également. [[w:simple:Special:NewArticle|Cette fonctionnalité]] fournit aux contributeurs des conseils élaborés par la communauté afin de les aider à créer des articles conformes aux normes communautaires. Les contributeurs expérimentés peuvent continuer à créer ou à adapter des modèles pour des types d'articles spécifiques qui sont couramment créés par des contributeurs moins expérimentés. Ces modèles guident les contributeurs moins expérimentés dans la création d'articles de haute qualité. Un guide rapide des balises utilisées dans les modèles est disponible sur [[mw:Special:MyLanguage/Article guidance/Test feature guide#Markups in outlines|cette page]]. [[w:fr:Projet:Aide à la rédaction d'articles#Liste de plans d'aide à la rédaction|Des exemples de modèles]] pouvant être adaptés, ainsi que des instructions sur la manière de les adapter, se trouvent dans [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|cette section]] de la page du projet.
* Les wikis qui souhaitent remplacer le bouton « indéfiniment » dans la page Special:Block pour les comptes temporaires (par exemple, les wikis qui bloquent les utilisateurs temporaires uniquement jusqu'à l'expiration de leur compte) pourront le faire en créant [[MediaWiki:ipb-indefinite-expiry-temporary-account]] avec la durée de blocage souhaitée. [https://phabricator.wikimedia.org/T427125]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:41|la tâche soumise|les {{formatnum:41}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:41||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* D'ici la fin du mois de juin, une chaîne « user-agent » valide sera requise pour les téléchargements automatisés de sauvegardes depuis le site dumps.wikimedia.org. Les requêtes automatisées fournissant une chaîne « user-agent » générique ou vide seront bloquées. Cette mesure [[phab:T400119|renforce l'application]] de la [[foundation:Special:MyLanguage/Policy:Wikimedia Foundation User-Agent Policy|politique relative à l'agent utilisateur]] en vigueur depuis longtemps. L'accès aux sauvegardes via Wikimedia Cloud Services restera inchangé.
* La mise en place des [[mw:Wikimedia APIs/Rate limits|limites de débit des API]] à l'échelle mondiale est désormais achevée ; ces limites s'appliquent à toutes les API et sont fixées aux niveaux indiqués dans la documentation pour tous les groupes. Les bots fonctionnant sur Toolforge/WMCS ou disposant du droit d'utilisateur « bot » sur n'importe quel wiki restent exemptés. Tous les bots doivent continuer à respecter les bonnes pratiques décrites dans la documentation afin d'éviter d'être soumis à des limites de débit.
* Le [https://api.wikimedia.org/wiki/Main_Page wiki du portail API] sera en lecture seule à partir de cette semaine (du 15 au 18 juin). La semaine suivante (du 22 au 25 juin), toutes les URL du wiki du portail API redirigeront vers [[mw:Wikimedia APIs|les API Wikimedia sur mediawiki.org]]. Pour en savoir plus, consultez la [[wikitech:API Portal/Deprecation|page du projet]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.7|MediaWiki]]
'''Rencontres et évènements'''
* Le 17 juin à 18 h (UTC), la WMF organisera une réunion sur Discord consacrée à la revue de code. L'[[mw:Special:MyLanguage/Developer Satisfaction Survey/2026|enquête sur la satisfaction des développeurs]] nous a permis de constater que les bénévoles rencontrent des difficultés avec la revue de code, et nous souhaitons discuter de ces expériences afin de trouver des solutions concrètes. Vous pouvez rejoindre la réunion [https://discord.gg/wikipedia?event=1514727511102062664 via le serveur Discord de la communauté Wikimedia].
* La [[m:Special:MyLanguage/Conferencia Wikimedia de América Latina 2026|Conférence Wikimedia d'Amérique latine]] organisera un hackathon régional qui réunira la communauté technique du mouvement Wikimedia, notamment des développeurs, des administrateurs système, des data scientists et des utilisateurs disposant de droits étendus. Les contributeurs techniques intéressés peuvent [https://docs.google.com/forms/d/e/1FAIpQLSf4osJzTHBJjQbYJk7TMVEJjTEQv7IgtsUDfP-o-qTgeRQQxw/viewform postuler à une bourse] pour y participer jusqu'au 21 juin à minuit (heure de la Bolivie, UTC-4).
* Inscrivez-vous aux Wikimania Team Challenges pour participer à cet événement exceptionnel. Les défis par équipe se dérouleront en ligne et en présentiel les 21 et 22 juillet, avant la conférence Wikimania. Tout le monde est le bienvenu, quelles que soient ses compétences ou son inscription à Wikimania. Les équipes travailleront sur 10 défis importants visant à soutenir la communauté Wikimedia. Pour plus de détails, rendez-vous sur [[wmania:Special:MyLanguage/2026:Team challenges|la page des défis par équipe]] et [https://wikimedia.eventyay.com/wm/teamchallenges/ inscrivez-vous ici]. Les inscriptions se terminent le 20 juin à 23 h UTC.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/25|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W25"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15 juin 2026 à 18:48 (CEST)
<!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30689604 -->
== Actualités techniques n° 2026-26 ==
<section begin="technews-2026-W26"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/26|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Les [[mw:Special:MyLanguage/Growth/Feature summary|fonctionnalités de croissance]] sont [[phab:T418115|désormais disponibles sur Wikidata]]. Cette mise à jour permet d'accéder au mentorat ([[mw:Special:MyLanguage/Help:Growth/Mentorship|s'il est configuré]]), au module Impact, au panneau d'aide et à une page d'accueil simplifiée pour les nouveaux arrivants (sans les suggestions de modifications). Les administrateurs de Wikidata continuent de paramétrer ces fonctionnalités via la configuration communautaire.
'''Actualités pour la contribution'''
* La page spéciale [[{{#special:RangeCalculator}}]] a été créée. Elle permet aux utilisateurs de trouver une plage d'adresses IP sans avoir à recourir à des outils externes. Jusqu'à présent, cet outil n'était accessible qu'aux CheckUsers. [https://phabricator.wikimedia.org/T268429]
* Les [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-références]] sont une nouvelle fonctionnalité de MediaWiki qui permet aux contributeurs de réutiliser des références en modifiant certains détails. Elle sera déployée le 23 juin, sur la plupart des versions de Wikipédia de petite et moyenne taille. La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#deployment|FAQ]] répertorie les mesures à prendre sur votre wiki pour faciliter ce déploiement. Consultez le [[:phab:T414094|plan de déploiement]] pour connaître les prochaines étapes. [https://phabricator.wikimedia.org/T428902]
* À partir de la semaine prochaine, les utilisateurs recevront une notification lorsqu'ils seront bloqués ou débloqués pour l'édition, ou si ce blocage venait à changer. [https://phabricator.wikimedia.org/T100974]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]].
'''Actualités pour la contribution technique'''
* À partir de la semaine prochaine, les filtres anti-abus configurés pour « exiger une vérification par CAPTCHA » s'appliqueront également aux utilisateurs disposant du droit <code>skipcaptcha</code>, ce qui inclut la plupart des utilisateurs auto-confirmés. Les bots en sont exemptés. Ce changement ne concerne que les modifications qui déclenchent un filtre anti-abus. Le droit <code>skipcaptcha</code> continuera à exempter les utilisateurs de l'obligation de résoudre des CAPTCHA dans le cadre d'une utilisation normale des wikis. [https://phabricator.wikimedia.org/T402595]
* La documentation de référence relative à l'[[wikitech:Machine_Learning/LiftWing/API|API Lift Wing]] a été déplacée du portail API vers le [https://wikitech.wikimedia.org/w/index.php?api=lift-wing&title=Special%3ARestSandbox bac à sable REST] interactif.
* Le wiki du Portail API est désormais fermé. Pour consulter la documentation relative aux API, rendez-vous sur [[mw:Special:MyLanguage/Wikimedia_APIs|Wikimedia APIs sur mediawiki.org]]. À compter du 22 juin, toutes les URL du wiki du Portail API (https://api.wikimedia.org/wiki/) redirigeront vers la page de mediawiki.org. [https://phabricator.wikimedia.org/T427537]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.8|MediaWiki]]
'''Rencontres et évènements'''
* Participez à une visioconférence le 25 juin à 14 h 30 UTC pour rencontrer les stagiaires actuels de Wikimédia participant au [[mw:Google_Summer_of_Code/2026|Google Summer of Code]] et à [[mw:Outreachy/Round_32|Outreachy]]. Les stagiaires présenteront leurs projets et feront une brève démonstration du travail qu'ils ont réalisé jusqu'à présent. Les participants sont invités à [[mw:event:Google_Summer_of_Code/Summer_2026_June_Internship_open_session|partager leurs idées et leurs contacts au sein de leur communauté]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/26|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W26"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 juin 2026 à 15:05 (CEST)
<!-- Message envoyé par User:Trizek (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30722494 -->
== RFC about AI-generated content in Wikimedia Commons ==
<bdi lang="en" dir="ltr">Apologies for writing in English, please help translate this message to your language. You are invited to participate in a [[c:Commons:Requests for comment/Policy update for AI content|request for comment on Wikimedia Commons about a policy update for AI content]]. This may affect files that are uploaded to Wikimedia Commons for use on this project. Thank you. [[m:User:Codename Noreste|Codename Noreste]] ([[m:User talk:Codename Noreste|discussion]])</bdi> 23 juin 2026 à 19:11 (CEST)
<!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== Intégration du lien vers les contacts juridiques et de sécurité dans le pied de page de votre wiki ==
<section begin="Message"/>
'''Contacts juridiques et de sécurité'''
Bonjour à toute la communauté, la Fondation Wikimedia a mis à disposition une [[wmf:Special:MyLanguage/Legal:Wikimedia Foundation Legal and Safety Contact Information|page unique dédiée aux mentions légales et à la sécurité]], à ajouter en pied de page de votre wiki, afin de garantir l'accès à des informations juridiques exactes. Il s'agit d'une exigence réglementaire. Nous avons déjà mis en place des liens vers les wikis en anglais, allemand, italien, espagnol et d'autres langues de Wikipedia, et nous les déploierons bientôt sur votre wiki. Pour en savoir plus, [[m:Special:MyLanguage/Wikimedia_Foundation_Legal_and_Safety_Contacts_FAQ|consultez la page du projet]] et n'hésitez pas à laisser vos commentaires dans ce fil de discussion ou sur la [[m:Special:MyLanguage/Talk:Wikimedia Foundation Legal and Safety Contacts FAQ|page de discussion]].
<section end="Message"/>
-- [[User:Sannita (WMF)|User:Sannita (WMF)]] ([[User talk:Sannita (WMF)|talk]]) 25 juin 2026 à 15:30 (CEST)
<!-- Message envoyé par User:Sannita (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=30731267 -->
== Actualités techniques n° 2026-27 ==
<section begin="technews-2026-W27"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/27|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Dans le cadre des [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|expériences sur la création de comptes]], l'équipe Growth a testé l'ajout d'une icône de compte utilisateur dans l'en-tête du site mobile pour les utilisateurs non connectés, offrant ainsi un accès direct aux actions « Créer un compte » et « Se connecter ». Cette expérience a permis d'augmenter le nombre de créations de comptes d'environ 20 % sans nuire à la qualité des modifications ni au taux de modifications constructives. Cette fonctionnalité sera désormais déployée sur tous les wikis de la Fondation Wikimédia sur le site mobile au cours de la première semaine de juillet. [https://phabricator.wikimedia.org/T428220]
* À la suite d'une [[phab:T426248|expérience concluante]], les utilisateurs connectés qui n'ont pas [[mw:Special:MyLanguage/Help:Email_confirmation|confirmé leur adresse e-mail]] lors de la création de leur compte voient s'afficher une nouvelle bannière leur demandant de finaliser cette procédure. Cela permet de réduire le risque que les utilisateurs se retrouvent bloqués hors de leur compte et rend les adresses e-mail associées aux comptes globalement plus fiables. Cette mesure s'inscrit dans le cadre du projet [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security|Sécurité des comptes]]. [https://phabricator.wikimedia.org/T428292]
* Une mise à jour de [[Special:Search|Recherche]] affine le comportement de <bdi lang="zxx" dir="ltr"><code><nowiki>-prefix:</nowiki></code></bdi> lorsqu'il est utilisé pour exclure des résultats. Auparavant, l'utilisation de <bdi lang="zxx" dir="ltr"><code><nowiki>-prefix:</nowiki></code></bdi> avec la négation pouvait élargir involontairement les résultats de recherche en ajoutant les espaces de noms inclus dans le champ de recherche, ce qui entraînait un comportement déroutant pour les utilisateurs s'attendant à un filtre d'exclusion simple. Avec cette mise à jour, <bdi lang="zxx" dir="ltr"><code><nowiki>-prefix:</nowiki></code></bdi> exclura désormais strictement les titres de pages correspondants comme prévu et pourra afficher un avertissement si l'espace de noms concerné n'a pas été explicitement sélectionné. Le comportement de <bdi lang="zxx" dir="ltr"><code><nowiki>prefix:</nowiki></code></bdi> sans négation reste toutefois inchangé. [https://phabricator.wikimedia.org/T427443]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:33|la tâche soumise|les {{formatnum:33}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:33||s}} la semaine dernière]]. Par exemple, le problème qui empêchait les réviseurs utilisant la barre d'outils « Page Curation » d'être automatiquement abonnés aux discussions qu'ils avaient lancées sur les pages de discussion a désormais été résolu. Les réviseurs recevront désormais des notifications lorsqu'une personne répondra à ces discussions. [https://phabricator.wikimedia.org/T329346]
'''Actualités pour la contribution technique'''
* À compter du 29 juin, les téléchargements automatisés depuis le site web « dumps.wikimedia.org » seront soumis à la [[Foundation:Special:MyLanguage/Policy:Wikimedia Foundation User-Agent Policy|politique relative aux user-agents]]. Les requêtes automatisées utilisant un user-agent générique ou vide seront bloquées. L'accès aux sauvegardes via Wikimedia Cloud Services n'est pas affecté. Cette mesure fait suite à l'annonce publiée dans le [[m:Special:MyLanguage/Tech/News/2026/25|numéro 2026/25 de Tech News]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.9|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/27|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W27"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 29 juin 2026 à 13:48 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30744833 -->
== Actualités techniques n° 2026-28 ==
<section begin="technews-2026-W28"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/28|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:34|la tâche soumise|les {{formatnum:34}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:34||s}} la semaine dernière]]. Par exemple, les résultats de la barre de recherche sur Wikidata étaient en anglais au lieu d’utiliser la bonne langue de repli pour les utilisateurs de variantes de langues. Ce problème a maintenant été corrigé : les suggestions de recherche suivront désormais correctement la chaîne des langues de repli. [https://phabricator.wikimedia.org/T429769]
'''Actualités pour la contribution technique'''
* En préparation de la [[m:Special:MyLanguage/Event:Celebrate Women|campagne Célébrons les femmes]] prévues pour mars 2027, l’[[m:Special:MyLanguage/Wikimedia Foundation/Advancement/Community Growth/Content Enablement|équipe Activation du contenu]] de Wikimedia Foundation a lancé un sondage composé de 22 questions pour mieux comprendre les contributions techniques des personnes s’identifiant comme femmes sur les projets Wikimedia. Répondre au sondage prend environ 15 à 20 minutes ; il restera ouvert jusqu’au 20 juillet 2026. Les [[m:Special:MyLanguage/Celebrate Women/Technical contributions survey|questions]] sont aussi sur wiki pour les regarder auparavant.
* L’[[mw:Special:MyLanguage/Extension:Score|extension Partitions]] prend désormais en charge le rendu de partition musicales comme images SVG, en plus du format PNG. Cela répond à [[:phab:T49578|une vieille demande]] et résout des problèmes anciens de qualité de l’image. Les deux formats sont désormais fournis aux clients web : PNG dans l’attribut <bdi lang="zxx" dir="ltr"><code><nowiki>src</nowiki></code></bdi> et SVG dans l’attribut <bdi lang="zxx" dir="ltr"><code><nowiki>srcset</nowiki></code></bdi>.
* Le nouvel analyseur syntaxique [[wikitech:Parsoid|Parsoid]] continue [[mw:Special:MyLanguage/Parsoid/Parser_Unification/Updates|d’être déployé sur d’autres wikis]], ce qui rend plus facile l’introduction de nouvelles fonctionnalités de lecture et de modification. Il a été activé sur Wikipédia en français, amenant la couverture totale à 78,9 % des pages vues de Wikipédia. Le déploiement sur Wikipédia en anglais sur ordinateur va progresser cette semaine.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.10|MediaWiki]]
'''En détails'''
* La [[diffblog:2026/06/29/wikimedia-hackathon-2026-building-collaborating-and-shaping-the-future-together/|publication sur le blog récapitulant]] le Hackathon Wikimedia 2026 est désormais en ligne. Il met en avant les projets, séances et activités sociales de l’évènement de cette année et présente les premières grandes lignes du Hackathon Wikimedia 2027.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/28|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W28"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 juillet 2026 à 15:56 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30773578 -->
== Actualités techniques n° 2026-29 ==
<section begin="technews-2026-W29"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/29|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* [[mw:Special:MyLanguage/Growth/Revise_Tone|Revise Tone]] aide les nouveaux contributeurs à identifier les passages des articles de Wikipédia susceptibles de contenir un langage non encyclopédique et les encourage à envisager d'en réviser le ton. Cette fonctionnalité a fait l'objet d'un [[w:en:A/B_testing|test A/B]] sur les Wikipédias en arabe, en anglais, en français et en portugais, où le taux d'achèvement des tâches par les nouveaux contributeurs [[mw:Special:MyLanguage/Growth/Revise_Tone#Experiment_Results|a augmenté de 38,7 %]] par rapport à la tâche de relecture par défaut, sans que la qualité des modifications n'en soit affectée. Le test s'est achevé le 9 juillet et la fonctionnalité est désormais accessible à tous sur ces wikis ; elle est configurable via la configuration communautaire. [[phab:T426364|Il est prévu]] de déployer « Revise Tone » sur d’autres wikis.
* La configuration communautaire permettant la [[mw:Special:MyLanguage/Help:Growth/Mentorship#Automated mentor list cleanup|suppression automatique des mentors inactifs]] selon des critères configurables sera activée le jeudi 16, [[mw:Special:MyLanguage/Growth/Deployment|sur certains wikis]], afin de maintenir à jour les listes de mentors. Les mentors sont des contributeurs expérimentés qui choisissent d'aider les nouveaux utilisateurs sur le wiki grâce aux [[mw:Special:MyLanguage/Growth/Feature summary|fonctionnalités de croissance]]. Les administrateurs peuvent désormais préparer les paramètres via [[w:Special:CommunityConfiguration/Mentorship|Special:CommunityConfiguration/Mentorship]] ; ces fonctionnalités prendront effet à partir de jeudi.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:38|la tâche soumise|les {{formatnum:38}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:38||s}} la semaine dernière]]. Par exemple, un problème où certains utilisateurs de l'application Android Wikipedia ont été déconnectés immédiatement après leur connexion, les empêchant de rester connectés et de modifier les pages, a maintenant été résolu. [https://phabricator.wikimedia.org/T316916]
'''Actualités pour la contribution technique'''
* La modification d'une page à l'aide de scripts utilisateur ou de gadgets entraînait la réinitialisation des étiquettes de liste de suivi que l'utilisateur avait attribuées à cette page. Ce problème a désormais été résolu. [https://phabricator.wikimedia.org/T423778]
* Pour contourner un bug de Safari (voir [[phab:T425211]]), sur les wikis utilisant Parsoid, wikilink hrefs utilise désormais des URL absolues au lieu d'URL relatives au protocole. La sortie de l'API REST reste inchangée et continue d'utiliser des URL relatives au protocole. Les gadgets, les scripts utilisateur, les bots et les feuilles de style CSS devront probablement être adaptés s'ils s'appuyaient sur la présence d'URL relatives au protocole dans wikilink hrefs. [https://phabricator.wikimedia.org/T431358]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.11|MediaWiki]]
'''En détails'''
* L'équipe chargée de la plateforme d'expérimentation de la Fondation Wikimedia a publié un article de blog faisant le bilan de sa première année d'expérimentation structurée. Cet article met en avant des expériences couronnées de succès, telles que « Paste Check », « Reference Check » et « Tone Check », qui ont permis d'améliorer les résultats des modifications et ont été étendues à un plus grand nombre d'utilisateurs, ainsi que des expériences qui n'ont pas abouti à des changements au niveau du produit. [[diffblog:2026/07/07/moving-the-needle-how-we-test-new-ideas-across-wikimedia-projects|En savoir plus]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/29|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W29"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 juillet 2026 à 18:11 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30804401 -->
== Actualités techniques n° 2026-30 ==
<section begin="technews-2026-W30"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/30|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L'[[mw:Special:MyLanguage/Reader/Reader Experience|équipe chargée de l'expérience utilisateur]] a pris en compte les commentaires de la communauté concernant l'emplacement des boutons « Watchstar » et « Watchlist » pour la [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|fonctionnalité bêta « Listes de lecture »]], qui permettrait d'enregistrer des articles pour les lire plus tard – un [[m:Special:MyLanguage/Community Wishlist/W102|élément de la liste de souhaits]] visant à intégrer cette fonctionnalité sur le site web. Les contributeurs sont invités à activer cette fonctionnalité bêta pour la tester et [[phab:T426453|partager leurs impressions]].
* Le [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|mode Suggestions]] propose des suggestions d'édition au sein de l'éditeur visuel afin d'améliorer les articles de Wikipédia. [[Special:EditChecks|Toutes les suggestions]] sont [[mw:Special:MyLanguage/Help:Suggestion mode#For administrators – local customization|configurables par la communauté]]. La fonctionnalité [[mw:Special:MyLanguage/Edit check/TextMatch|TextMatch]] permet aux bénévoles de créer des suggestions locales personnalisées. Cette fonctionnalité recherche des chaînes de texte dans les articles et prend désormais en charge les expressions régulières. Cela offre aux bénévoles davantage de précision et de flexibilité quant aux types de suggestions locales qu’ils peuvent créer. Remarque : les suggestions [[mw:Special:MyLanguage/Edit check/Configuration#:~:text=maximumEditcount|peuvent être ciblées]] en fonction du nombre de modifications effectuées par la personne qui édite, ainsi que d’autres aspects de la page. Vous pouvez [[mw:Special:MyLanguage/Help:Suggestion mode#Create custom local types of Suggestions|trouver des exemples provenant d’autres communautés]] pour vous inspirer, notamment des TextMatches qui détectent : les fautes de frappe, les erreurs grammaticales, les publicités potentielles, les clichés, l’utilisation incorrecte des tirets ou des traits d’union, les mots-clés temporels non spécifiques, les noms obsolètes, et bien plus encore. Tout [[mw:Special:MyLanguage/Talk:VisualEditor/Suggestion Mode|retour]] est le bienvenu.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, un problème lié à l'outil SVG Translate, qui pouvait utiliser une version obsolète d'un fichier, entraînant ainsi le remplacement des traductions existantes lors du téléchargement de nouvelles traductions, a désormais [[phab:T430577|été résolu]]. Au total, au cours du dernier trimestre, d'avril à juin 2026, environ 337 tâches communautaires ont été résolues par la Fondation Wikimedia.
'''Actualités pour la contribution technique'''
* Sur les wikis utilisant Parsoid, ce dernier affiche désormais un maximum de 1 250 images par page. Une nouvelle catégorie de suivi, « media-limit-reached », sera bientôt disponible pour identifier les pages où cette limite est atteinte, ce qui facilitera la recherche des contenus dont l'affichage des médias a pu être restreint lors du rendu. Consultez [[phab:T430854]] pour plus d'informations et pour nous faire part de vos commentaires.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.12|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/30|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W30"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 21 juillet 2026 à 07:46 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30836091 -->
== Request for comment (the future of Abstract Wikipedia) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies if this has not yet been translated into your wiki's language. {{int:Please-translate}}
You are invited to voice your opinions in a [[:m:Requests for comment/The future of Abstract Wikipedia|request for comment about the future of Abstract Wikipedia]]. {{Int:Feedback-thanks-title}}
[[:m:User:Kowal2701|Kowal2701]] ([[:m:User talk:Kowal2701|talk]]) 24 juillet 2026 à 14:24 (CEST)
</bdi>
<!-- Message envoyé par User:DreamRimmer@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== <span lang="en" dir="ltr">Tech News: 2026-31</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W31"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/31|Translations]] are available.
'''Updates for editors'''
* [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Wishlist item]] [[mw:Special:MyLanguage/ContentTranslation|Content Translation]] now supports dark mode, fulfilling a [[m:Community Wishlist/W544|Community Wishlist request]]. This brings the tool in line with the accessibility features available in the Vector 2022 and Minerva skins, helping reduce visual fatigue for users translating content. [https://phabricator.wikimedia.org/T367077]
* DiscussionTools' source mode and the 2017 wikitext editor will now offer autocomplete for links (<bdi lang="zxx" dir="ltr"><code><nowiki>[[</nowiki></code></bdi>), templates (<bdi lang="zxx" dir="ltr"><code><nowiki>{{</nowiki></code></bdi>), HTML and parser tags (<bdi lang="zxx" dir="ltr"><code><nowiki><</nowiki></code></bdi>), and magic words (<bdi lang="zxx" dir="ltr"><code><nowiki>__</nowiki></code></bdi>), making it quicker and easier to insert links, templates, and other wiki markup while editing. [https://phabricator.wikimedia.org/T432400]
* The [[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews|Readers Growth team]] has concluded its experiment with mobile page previews and will not roll out the feature. Page Previews are a pop-up bottom sheet that appears when readers tap a blue link, showing a thumbnail, lead paragraph, and an option to open the article. The experiment showed flat retention and negative indicator metrics, suggesting that mobile web readers preferred navigating directly to linked articles rather than using page previews.
* The Reader Experience team has seen encouraging early results from the [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Reading Lists feature]], with 93% of participating users reporting that it was useful. Reading Lists help active readers save articles for future reading and support their learning goals on Wikimedia projects. The team plans further improvements before expanding the feature to more users.
* The [[mw:Special:MyLanguage/Wikimedia Apps/Team/Explore Feed Refresh|Explore Feed Refresh]] initiative was tested with new and casual Wikipedia app readers. The refreshed feed helps readers discover new and relevant content. After a 10.5% increase in engagement with the feed, Wikimedia Apps team has decided to scale the Home Feed redesign to iOS with the learnings from the Android release applied.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:23}} community-submitted {{PLURAL:23|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where subject names in the Article Guidance feature were displayed with incorrect capitalization on French Wikipedia, has now been fixed. Subject names will now follow the correct capitalization rules for the language. [https://phabricator.wikimedia.org/T427201]
'''Updates for technical contributors'''
* After running several [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|Account Creation Experiments]] to improve registration completion rates, a new version of the username field on [[Special:CreateAccount|Create Account]] has been rolled out. It includes [[:c:File:Create account - July 2026 updates.png|a popover summarizing the username policy]] to provide clearer guidance during account creation. As part of this change, the messages <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-helpusername</nowiki></code></bdi> and <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-help</nowiki></code></bdi> that several communities have configured will no longer be used. If communities want to customize the guidance shown in the new popover, they can instead edit the following messages: <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-policy-popover-bullet1</nowiki></code></bdi>, <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-policy-popover-bullet2</nowiki></code></bdi>, and <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-policy-popover-bullet3</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T430604]
* Later this week, the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror syntax highlighter]] will offer [[w:en:Theme (computing)|themes]]. The themes can be picked from a dropdown menu in the full [[mw:Special:MyLanguage/Help:Extension:CodeMirror#CodeMirror preferences|CodeMirror preferences]] dialog. For wikitext, available themes are default, colorblind-friendly (previously the colorblind preference option on [[Special:Preferences#mw-prefsection-editing]]) and no-highlighting. For code languages (i.e., CSS/JavaScript/JSON/Vue/Lua), there are several themes available. These same themes will eventually be available for wikitext, too. [https://phabricator.wikimedia.org/T163533]
* From now on, wikis can restrict editing in the "User" namespace to only the page owner and certain user groups. [[mw:Special:MyLanguage/Manual:$wgRestrictUserPageEditing|Read the configuration documentation]] to learn more.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.14|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/31|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W31"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 juillet 2026 à 20:49 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30856308 -->
== Actualités techniques n° 2026-32 ==
<section begin="technews-2026-W32"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/32|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* L’[[mw:Special:MyLanguage/Readers/Reader Experience|équipe Expérience de lecture]] a développé une [https://82db7c8d4b.catalyst.wmcloud.org/w/index.php?title=Regent%27s_Park&uselang=de démonstration d’un correctif] qui entoure la barre d’outils sur deux lignes quand il n’y a pas assez d’espace horizontalement pour tous les boutons. Le but est de réduire l’encombrement de la barre d’outil de Vector 2022 qui se constate parfois sur Wikipédia dans certaines langues avec certaines tailles d’écran. [https://phabricator.wikimedia.org/T429518]
* L’équipe Expérience de lecture prévoit de lancer les [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Listes de lecture]], un [[m:Special:MyLanguage/Community Wishlist Survey 2021/Mobile and apps/Have Apps reading lists available on Destop/Mobile|souhait de la communauté]] que vous pouvez déjà essayer en bêta, à partir de septembre. Mais avant cela, l’aide des traducteurs et traductrices est nécessaire pour [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-readinglists&filter=&action=translate plusieurs messages] à traduire dans un maximum de langues. Cette fonctionnalité appuie les objectifs de lecture et d’apprentissage sur Wikipédia.
* La semaine prochaine, le sommaire sur les pages des fichiers de Wikimedia Commons sera amélioré en fusionnant le sommaire spécifique aux pages de fichier avec le sommaire classique des pages. Cela rendra plus facile la compréhension de la structure des pages de fichier, la navigation vers une section spécifique et le partage de liens vers une section en particulier. [https://phabricator.wikimedia.org/T332644]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:24|la tâche soumise|les {{formatnum:24}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:24||s}} la semaine dernière]]. Par exemple, certaines images [[w:fr:Tagged Image File Format|TIFF]] ne s’affichaient pas après avoir cliqué sur leur vignette : une image cassé s’affichait au lieu de l’image en taille réelle. Ce problème est désormais corrigé. [https://phabricator.wikimedia.org/T429326]
'''Actualités pour la contribution technique'''
* Le sélecteur de variables et de fonctions dans les Filtres anti-abus a été mis à jour pour prendre en charge la recherche et l’autocomplétion. Il permettra désormais aux mainteneurs de filtres de retrouver la variable ou fonction désirée plus rapidement. [https://phabricator.wikimedia.org/T323698]
* Les formats MJPEG et VP8 sont retirés du lecteur vidéo. Le format MP4 (MPEG-4 Partie 2) est ajouté à la place : il fournit une meilleure qualité pour les vieux modèles d’iPhone. Plusieurs semaines seront peut-être nécessaires pour mettre à jour toutes les vidéos déjà en ligne. Le format par défaut pour les appareils modernes ne change pas (VP9/WebM). [https://phabricator.wikimedia.org/T358266]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.15|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/32|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W32"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 3 août 2026 à 21:46 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30872536 -->
== Actualités techniques n° 2026-33 ==
<section begin="technews-2026-W33"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/33|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] Un nouvel assistant à diagrammes (''ChartWizard'') est [[c:Special:ChartWizard/Data:Example.Pie.chart|désormais disponible sur Wikimedia Commons]] pour les utilisateurs et utilisatrices qui souhaitent créer des graphiques à partir de leurs propres données. Cet assistant rend l’[[mw:Special:MyLanguage/Extension:Chart|extension Chart]] plus accessible aux débutants en permettant la création de diagrammes (par exemple des histogrammes ou des graphiques en secteurs) sans devoir utiliser du JSON. Les utilisateurs peuvent encore repasser à l’éditeur JSON s’ils le préfèrent. Vos retours sur ce nouvel outil sont les bienvenus sur la [[m:Talk:Community Wishlist/W414|page de discussion du souhait]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]]. Par exemple, le gadget Photo du jour de l’appli Wikipédia pour iOS affichait par erreur la même image chaque jour au lieu d’en afficher une nouvelle quotidiennement. Cela a désormais été corrigé. [https://phabricator.wikimedia.org/T430692]
'''Actualités pour la contribution technique'''
* Les images SVG de [[mw:Special:MyLanguage/Extension:Math|formules mathématiques]] seront bientôt générées par le navigateur plutôt que côté serveur. MathML continue d’être généré côté serveur et est rendu dans le navigateur sans JavaScript. Ce changement aura lieu sur Wikibooks le 12 aout, sur Wikisource le 19 aout et sur Wikipédia du 20 au 27 aout. Vous pouvez l’essayer en choisissant « {{int:Mw-math-mathjax}} » dans vos préférences. Cela fait partie de la [[mw:Special:MyLanguage/RESTBase/deprecation|dépréciation de RESTBase]] et [[phab:T431372|de celle de Mathoid]]. [https://phabricator.wikimedia.org/T271001]
* <span class="mw-translate-fuzzy">Les pages de catégorie permettront bientôt de trier les pages de la catégorie par ordre chronologique d’ajout dans la catégorie. Cela permettra de retrouver facilement les pages récemment ajoutées ou celles depuis longtemps dans la catégorie. Cela améliorera aussi le traitement des catégories de maintenance, par exemple les demandes de suppression ou d’autres tâches nécessitant un traitement chronologique. Utilisez l’argument <bdi lang="zxx" dir="ltr"><code><nowiki>cldsort=timestamp</nowiki></code></bdi> dans l’URL de la vue d’une catégorie pour trier la liste de pages.</span> [https://phabricator.wikimedia.org/T433768]
* Les [[mw:Special:MyLanguage/Extension:Gadgets|gadgets]] et scripts utilisateur sur les wikis Wikimedia peuvent désormais utiliser les [[phab:T395347|fonctionnalités de ES 2018]] et [[phab:T419142|celles de ES 2019]] dans le code JavaScript. Auparavant, la plateforme n’acceptait qu’ES 2017. MediaWiki valide le code source pour protéger les fonctionnalités d’erreurs de syntaxes et pour vérifier que les scripts sont fonctionnels dans tous [[mw:Special:MyLanguage/Compatibility#Browser_support_matrix|les navigateurs pris en charges]]. [https://phabricator.wikimedia.org/T419142]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.16|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/33|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W33"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 août 2026 à 22:45 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30901051 -->
== <span lang="en" dir="ltr">Tech News: 2026-34</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W34"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/34|Translations]] are available.
'''Weekly highlight'''
* The [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Worklist|Worklist feature]] for the Event Registration tool is now live on all Wikimedia wikis. With Worklist, event organizers can add the articles their event will focus on directly to the event page. The Worklist also powers [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Worklist#How Event Pathways uses the worklist|Event Pathways]] which notifies other editors of the upcoming or ongoing event when they edit an article featured in the event's Worklist. This is the minimum viable version (MVP), and feedback is welcome. Organizers are encouraged to try the feature. A hands-on [[m:Special:MyLanguage/Event:Worklist Setup Workshop: Get Your Event Ready|Worklist Setup Workshop]] will take place on 18 August at 16:00 UTC and 19 August at 11:00 UTC.
'''Updates for editors'''
* [[Special:ShortPages]] displays short pages by their size, but in many cases it gets filled with disambiguations and soft redirects, making it harder to find the short articles themselves. Starting this weekend, you will be able to choose not to include an article in the special page by adding the magic word <bdi lang="zxx" dir="ltr"><code><nowiki>__EXPECTSHORTPAGE__</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T433203]
* One new wiki has been created: a {{int:project-localized-name-group-wikipedia/en}} in [[d:Q3436680|Bole]] ([[w:bol:|<bdi lang="zxx" dir="ltr"><code><nowiki>w:bol:</nowiki></code></bdi>]]) [https://phabricator.wikimedia.org/T429921]
* Starting the week of August 17, the page toolbar will wrap onto two lines when there is not enough horizontal space for all the buttons. This is a [[phab:T429518|fully merged patch from the Reader Experience team]] which aims to reduce crowding in the Vector 2022 toolbar, that may occur on some language Wikipedias at certain screen widths.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:16}} community-submitted {{PLURAL:16|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, uploading large files to Wikimedia Commons has become more stable and less prone to failure following some fixes related to the “Could not acquire lock” upload error. [https://phabricator.wikimedia.org/T386640]
'''Updates for technical contributors'''
* Debian Bullseye will reach the end of its Long Term Support on 31 August 2026. [[phab:T434103|Some Cloud VPS projects]] still have instances running Debian Bullseye. Maintainers of those projects are encouraged to migrate to Debian Bookworm or Debian Trixie. A [[wikitech:Help:Cloud VPS instance operating system migration|migration guide]] is available to help with the process, and users may also want to consider whether their workload is better suited to Toolforge. If you need help or cannot complete the migration by 31 August, please contact the Cloud VPS admins as soon as possible. [[listarchive:list/cloud-announce@lists.wikimedia.org/thread/RVIPQSYLKMSL5M46JP6NEJVGVE6I2RXQ/|Read more]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.16|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/34|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W34"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 17 août 2026 à 23:03 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30922806 -->
== Actualités techniques n° 2026-35 ==
<section begin="technews-2026-W35"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/35|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* La page [[Special:CreateAccount|Spécial:Créer_un_compte]] a été simplifiée dans le cadre des travaux en cours visant à moderniser le processus de création de compte. Le panneau affichant les statistiques du projet n'apparaît plus à côté du formulaire, que ce soit sur ordinateur ou sur mobile. Plusieurs tests portant sur la création de comptes ont montré qu'un formulaire plus simple aidait les nouveaux utilisateurs à mener à bien leur inscription. [https://phabricator.wikimedia.org/T433783]
* Afin d'améliorer les performances de la page, les images ne se chargent désormais que lorsqu'elles sont visualisées. Cela signifie que les images situées plus bas dans un article ne se chargeront pas si le lecteur ne fait jamais défiler la page jusqu'à cette partie, ce qui peut avoir une incidence sur certains indicateurs liés aux images. [https://phabricator.wikimedia.org/T148047]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:42|la tâche soumise|les {{formatnum:42}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:42||s}} la semaine dernière]]. Par exemple, un problème empêchant l'affichage des vignettes d'images sur Abstract Wikipedia après le déplacement du fichier correspondant sur Wikimedia Commons a désormais été résolu. Les vignettes s'actualisent désormais correctement lorsque les fichiers sont déplacés. [https://phabricator.wikimedia.org/T433448]
'''Actualités pour la contribution technique'''
* La fiche d'informations sur l'utilisateur est une fonctionnalité qui permet aux modérateurs de consulter des informations sur les comptes d'utilisateurs. Jusqu'à présent, elle n'était disponible que dans des sections telles que l'historique des pages, les journaux et les modifications récentes. Désormais, il est également possible de [[mw:Special:MyLanguage/Help:Extension:CheckUser#User_Info_card_in_page_content|l'insérer dans le contenu d'une page]] à l'aide de la fonction d'analyse <bdi lang="zxx" dir="ltr"><code><nowiki>{{#uic:}}</nowiki></code></bdi>. Cela peut s'avérer particulièrement utile dans des modèles tels que [[:en:Template:Userlinks|<bdi lang="zxx" dir="ltr"><code><nowiki>{{Userlinks}}</nowiki></code></bdi>]] (ou leurs variantes spécialisées), car cela facilitera la compréhension du contexte relatif à un ou une utilisatrice sur les divers forums communautaires. La fiche ne s'affichera que pour les utilisateurs qui l'ont activée dans leurs [[Special:Preferences#mw-input-wpcheckuser-userinfocard-enable|préférences]]. [https://phabricator.wikimedia.org/T424466]
* En raison des risques liés à la sécurité et à la confidentialité des utilisateurs, nous avons désactivé l'accès aux URL utilisant <bdi lang="zxx" dir="ltr"><code><nowiki>Special:MyPage</nowiki></code></bdi> (Spécial:Ma_page) avec l’argument <bdi lang="zxx" dir="ltr"><code><nowiki>action=raw</nowiki></code></bdi>. Si vous êtes concerné par cette mesure, demandez-vous s'il est possible d'adopter une autre approche. Les URL utilisant <bdi lang="zxx" dir="ltr"><code><nowiki>Special:MyPage</nowiki></code></bdi> restent accessibles et utilisables sans <bdi lang="zxx" dir="ltr"><code><nowiki>action=raw</nowiki></code></bdi>. Les URL de pages utilisateur spécifiées (par exemple, <bdi lang="zxx" dir="ltr"><code><nowiki>User:Myusername</nowiki></code></bdi>) peuvent toujours être utilisées avec <bdi lang="zxx" dir="ltr"><code><nowiki>action=raw</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T120386]
* Suite à une mise à jour, le logiciel de création de vignettes a été amélioré. En effet, <bdi lang="zxx" dir="ltr"><code><nowiki>librsvg</nowiki></code></bdi> a été mis à jour vers la version 2.60 et <bdi lang="zxx" dir="ltr"><code><nowiki>ImageMagick</nowiki></code></bdi> vers la version 7. Cela a permis la résolution d’un certain nombre de bugs de longue date liés à la création de vignettes, tels que des erreurs de rendu. [https://phabricator.wikimedia.org/T419815#12222841]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.17|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/35|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W35"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 24 août 2026 à 22:45 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30961208 -->
== Actualités techniques n° 2026-36 ==
<section begin="technews-2026-W36"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/36|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* Un nouveau format pour la liste de souhaits de la communauté est ouvert aux commentaires. Vous pouvez [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027|lire les idées proposées sur Meta]]. Ce nouveau processus prévoit d'améliorer la manière dont les souhaits sont triés, votés et priorisés d'une manière transparente et équilibrée entre les familles de projets et les éditions linguistiques. Cette consultation est ouverte pendant deux semaines.
'''Actualités pour la contribution'''
* La dernière version de l'application Wikipédia pour Android comprend des mises à jour de la fonctionnalité «Enregistrés», qui rapprochent l'expérience d'enregistrement de l'application de celle proposée sur iOS et sur le Web. Cette mise à jour repense l'onglet «Enregistrés» en proposant une vue «Tous les articles», supprime la liste de lecture par défaut «Enregistrés», renomme les listes de lecture «Collections» et modernise l'expérience de sauvegarde des articles. [https://phabricator.wikimedia.org/T420788]
* La fonctionnalité [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Listes de lecture]] a été activée pour tous les utilisateurs connectés sur les versions de Wikipédia en bengali, chinois, tchèque et vietnamien le 25 août, après plusieurs mois en tant que fonctionnalité bêta. Les listes de lecture seront disponibles pour tous les utilisateurs connectés sur les versions de Wikipédia en arabe, français et indonésien le 1er septembre, suivies par la Wikipédia en anglais le 14 septembre, et toutes les autres versions de Wikipédia le 28 septembre.
* À la fin du mois, certains lecteurs non connectés sur les versions en bengali, tchèque, persan, anglais et polonais de Wikipédia utilisant l'habillage Minerva sur mobile verront une [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|barre de navigation mise à jour]] dans le cadre d'un [[w:A/B test|test A/B]]. Le test comparera la barre de navigation actuelle avec une nouvelle version conçue pour faciliter la recherche d'informations plus rapidement. L'objectif est de déterminer si ces changements encouragent les lecteurs à revenir plus souvent. Cette expérience n'aura aucune incidence sur l'expérience des lecteurs et/ou des rédacteurs connectés.
* Les contributeurs qui gèrent les redirections, les modèles et les catégories utilisés sur les pages de redirection disposent désormais de meilleurs moyens pour trouver et organiser les redirections. Auparavant, il n'était pas possible de rechercher les pages de redirection. Deux nouveaux mots-clés de recherche, <bdi lang="zxx" dir="ltr"><code><nowiki>onlyredirects:</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>withredirects:</nowiki></code></bdi>, permettent désormais de rechercher directement les redirections et peuvent être combinés avec des mots-clés existants tels que <bdi lang="zxx" dir="ltr"><code><nowiki>incategory:</nowiki></code></bdi>, <bdi lang="zxx" dir="ltr"><code><nowiki>intitle:</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>insource:</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T204089]
* Les outils de recherche ISBN pour générer des citations ne fonctionnaient pas récemment en raison de problèmes de service externe. Les développeurs travaillent sur des solutions. [https://phabricator.wikimedia.org/T435179]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, un problème où la recherche de pages par catégorie à l'aide de <bdi lang="zxx" dir="ltr"><code><nowiki>deepcat</nowiki></code></bdi> pouvait ne renvoyer aucun résultat ou des résultats sans rapport a été corrigé. [https://phabricator.wikimedia.org/T414859]
'''Actualités pour la contribution technique'''
* Le domaine des URL pour les miniatures passe de upload.wikimedia.org à thumb.wikimedia.org. Les anciennes URL continueront de fonctionner pour le moment, mais MediaWiki utilisera le nouveau domaine à la place. Les URL vers d'autres types de médias tels que les fichiers originaux, les vidéos et les transcodages seront toujours servies depuis upload.wikimedia.org. [https://phabricator.wikimedia.org/T427465]
* L'API Math de Wikimédia [https://www.mediawiki.org/w/index.php?api=wmf-math%2Fv1&title=Special%3ARestSandbox] est désormais obsolète. Ces points de terminaison seront complètement arrêtés d’ici la fin de septembre 2026. Les développeurs qui appellent ces points de terminaison devraient passer à des solutions alternatives de rendu mathématique, telles que le [https://developer.mozilla.org/en-US/docs/Web/MathML MathML] natif ou [https://www.mathjax.org/ MathJax]. Les installations tierces de MediaWiki utilisant l'extension Math pour le rendu des formules doivent effectuer une mise à jour vers la version 1.43+ afin d'éviter toute interruption de service.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.18|MediaWiki]]
'''En détails'''
* Lisez-en plus sur [[mw:Special:MyLanguage/Edit_check/TextMatch|TextMatch]] dans un article de Diff intitulé [[diffblog:2026/08/28/custom-edit-suggestions-for-every-wiki-how-communities-are-shaping-suggestion-mode-with-textmatch/|Suggestions de modifications personnalisées pour chaque wiki : comment les communautés façonnent le mode Suggestion avec TextMatch]].
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/36|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W36"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 31 août 2026 à 22:53 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30990659 -->
== Actualités techniques n° 2026-37 ==
<section begin="technews-2026-W37"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/37|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* La fonctionnalité [[mw:Special:MyLanguage/Help:Growth/Tools/Add_a_link|Ajouter un lien]] a été améliorée pour les utilisateurs de Wikipédia en anglais, avec une meilleure détection des liens présentant des différences de majuscules. Cela permettra de réduire les suggestions ambiguës dues aux différences de majuscules dans les titres d'articles, notamment ceux consacrés aux biens culturels. Une deuxième phase d'améliorations est prévue, qui permettra également de préparer cette fonctionnalité en vue de son déploiement dans d'autres langues. [https://phabricator.wikimedia.org/T435526#12240876] [https://phabricator.wikimedia.org/T434259]
* La fonctionnalité [[mw:Special:MyLanguage/Article_guidance|Guide des articles]] sera activée par défaut pour les contributeurs débutants sur Wikipédia en anglais simplifié et en turc à compter du 10 septembre 2026, à la suite d'[[mw:Special:MyLanguage/Article_guidance/Updates#Summary_of_experiment_result|une expérience]]. Les contributeurs débutants ayant effectué entre 0 et 99 modifications verront automatiquement s'afficher cette fonctionnalité lorsqu'ils cliqueront sur un lien rouge ou utiliseront l'option « [[tr:Vikipedi:Madde_sihirbazı|Madde oluşturto]] » sur Wikipédia en turc pour créer un nouvel article. Ce changement vise à aider les contributeurs débutants à créer des articles de meilleure qualité, conformes aux normes de chaque Wikipédia. [[phab:maniphest/query/z3tcTjmLMxk5/#R|D'autres améliorations]] seront apportées en fonction des résultats de l'expérience et des retours de la communauté.
* L'outil [https://pageviews.wmcloud.org « Analyse des pages vues »], qui permet aux utilisateurs de comparer le nombre de pages vues sur plusieurs pages, fête cette année ses 10 ans et s'est enrichi de plusieurs nouvelles fonctionnalités. Parmi celles-ci figurent [[toolforge:wikinav|WikiNav]], qui donne un aperçu de la manière dont les lecteurs de Wikipédia explorent le contenu, les statistiques d'édition dans [https://pageviews.wmcloud.org/siteviews?range=latest-30&sites=en.wikipedia.org « Vues du site »], la possibilité de [https://pageviews.wmcloud.org/massviews?source=wikiproject&project=en.wikipedia.org rechercher des articles appartenant à un projet Wiki] et la prise en charge du mode sombre. [https://phabricator.wikimedia.org/T378549]
* Une barre de navigation [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|Minerva, visuellement simplifiée]] est actuellement testée auprès des lecteurs non connectés utilisant la version mobile des Wikipédias en bengali, tchèque, anglais, farsi et polonais. Cette expérience vise à déterminer si la simplification de la navigation permet de fidéliser davantage les lecteurs. Le test se déroulera du 31 août au 28 septembre et ne nécessite aucune intervention de la part des utilisateurs.
* [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] travaille à l'amélioration des [[en:Wikipedia:VisualEditor/Named references|noms de référence générés automatiquement dans VisualEditor]]. Les contributeurs ne remarqueront qu'un léger changement à partir de cette semaine. Lors de l'ajout de noms de référence automatiques, la numérotation commencera par <bdi lang="zxx" dir="ltr"><code><nowiki>:1</nowiki></code></bdi> au lieu de <bdi lang="zxx" dir="ltr"><code><nowiki>:0</nowiki></code></bdi>. Pour en savoir plus, consultez la [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|page du projet]]. [https://gerrit.wikimedia.org/r/c/VisualEditor/VisualEditor/+/1332704]
* Tous les wikis seront [[m:Special:MyLanguage/Tech/Server switch|en lecture seule pendant quelques minutes]] le 23 septembre. Cette opération est prévue à 14h UTC. De plus amples informations seront publiées dans la rubrique « Tech News » et seront également mises en ligne sur chaque wiki au cours des prochaines semaines. [https://phabricator.wikimedia.org/T433363]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:25|la tâche soumise|les {{formatnum:25}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:25||s}} la semaine dernière]]. Par exemple, un problème lié au fait que [[mw:Special:MyLanguage/Parsoid|Parsoid]] pouvait mal gérer les balises nowiki imbriquées, entraînant la perte de contenu et l'affichage de texte indésirable, a désormais été corrigé. [https://phabricator.wikimedia.org/T435116]
'''Actualités pour la contribution technique'''
* Les administrateurs de l'interface peuvent configurer les gadgets à partir de [[MediaWiki:Gadgets-definition]]. Le format de définition a été mis à jour et ne nécessite plus l'option <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi>, car les gadgets sont toujours chargés via <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi>. Cela simplifie la configuration des gadgets en supprimant une option qui n'est plus nécessaire, ce qui facilite la tâche des administrateurs lors de la définition des gadgets. [https://phabricator.wikimedia.org/T298199]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.19|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/37|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W37"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 7 septembre 2026 à 20:44 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31008437 -->
== <span lang="en" dir="ltr">Tech News: 2026-38</span> ==
<div lang="en" dir="ltr">
<section begin="technews-2026-W38"/><div class="plainlinks">
Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/38|Translations]] are available.
'''Weekly highlight'''
* The Future Audiences team has started a [[en:Special:MyLanguage/Wikipedia:Village pump (proposals)#New proposed experiment to let readers know about “Google preferred sources”|discussion on English Wikipedia]] about a proposed new experiment to show a temporary notice about the new [https://blog.google/products-and-platforms/products/search/original-high-quality-content-search/ Google Preferred Sources] feature to Wikipedia readers coming from Google. The experiment is to test whether this notice makes visitors come back to Wikipedia more often. Preferred Sources feature lets users choose websites they trust so Google can highlight content from those sources more prominently in Search and AI experiences. We are interested in testing this on other languages that Google supports and would welcome assistance with starting conversations on other wikis. If interested, please reach out to Future Audiences on the [[m:Special:MyLanguage/Future Audiences/Preferred sources|talk page]].
'''Updates for editors'''
* The chat platform Discord is releasing a new self-service framework that websites can use to specify how their links should look when shared on Discord. The Future Audiences team is planning to start using this new framework to improve how Wikipedia links look when shared on Discord, giving better attribution and credit to contributors. The link appearance and behavior won't change in the first phase of this project as we want to first collect some baseline data to assess the impact of future changes. However, if community members who are active on Discord spot any issues, they can reach out on [[phab:tag/future-audiences/|Phabricator]] or [[m:Special:MyLanguage/Future Audiences|Metawiki]].
* The Reader Growth team will be running an experiment to test whether [[mw:Special:MyLanguage/Readers/Reader_Growth/Compact Lead|compacting article lead sections on mobiles]] with the addition of a "read more" button improves reader retention. The test will begin on September 17 on Arabic, Spanish, French, Indonesian, Italian, Japanese, Portuguese, Vietnamese, and Chinese Wikipedias and will run for four weeks.
* All wikis will be [[m:Special:MyLanguage/Tech/Server switch|read-only for a few minutes]] on September 23. This is planned at 14:00 UTC. More information will be published in Tech News and will also be posted on individual wikis in the coming week. [https://phabricator.wikimedia.org/T433363]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [https://wikistats.wmcloud.org/ Wikistats] for Wikipedias was returning an HTTP 500 error and could not be reached, has now been fixed. [https://phabricator.wikimedia.org/T435959]
'''Updates for technical contributors'''
* Developers who maintain a tool that queries the Wikimedia Commons links tables need to update their code to connect to the new x4 database cluster. The links tables have been moved from the s4 cluster to x4, and will no longer receive updates on s4. The page and redirect tables remain available on both clusters. A wiki replica for the x4 cluster will be set up afterwards. The change is being made because the s4 cluster has grown too large to operate efficiently. You can [[wikitech:Special:MyLanguage/News/2026 Commons links tables database split|read more]].
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.20|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]] • [[m:Special:MyLanguage/Tech/News#contribute|Contribute]] • [[m:Special:MyLanguage/Tech/News/2026/38|Translate]] • [[m:Tech|Get help]] • [[m:Talk:Tech/News|Give feedback]] • [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].''
</div><section end="technews-2026-W38"/>
</div>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 14 septembre 2026 à 17:31 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31039826 -->
== Bascule de serveur : votre wiki sera bientôt en lecture seule durant un court instant ==
<section begin="server-switch"/><div class="plainlinks">
[[:m:Special:MyLanguage/Tech/Server switch|Lire ce message dans une autre langue]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}]
La [[foundation:|Fondation Wikimédia]] va basculer le trafic entre ses centres de données. Cela permettra de s’assurer que Wikipédia et les autres wikis de Wikimédia peuvent rester en ligne même après une catastrophe.
Le trafic sera basculé le '''{{#time:j xg|2026-09-23|fr}}'''. La bascule débutera à '''[https://zonestamp.toolforge.org/{{#time:U|2026-09-23T14:00|en}} {{#time:H:i e|2026-09-23T14:00}}]'''.
Malheureusement, en raison de certaines limites de [[mw:Special:MyLanguage/Manual:What is MediaWiki?|MediaWiki]], toutes les modifications de pages devront être arrêtées durant le passage d’un centre de données à l’autre. Nous nous excusons pour ce dérangement, que nous nous efforçons de réduire pour le futur.
Une bannière sera affichée sur tous les wikis 30 minutes avant le début de l’opération. Cette bannière restera visible jusqu’à la fin de l’opération.
Vous pouvez contribuer à la [https://meta.wikimedia.org/w/index.php?title=Special%3ATranslate&group=Centralnotice-tgroup-read_only_banner&task=view&language=&filter=&action=translate traduction ou relecture] du texte de cette bannière.
'''Pendant une courte période, vous pourrez lire les wikis mais pas les modifier.'''
* Vous ne pourrez pas effectuer de modification pendant une durée pouvant aller jusqu’à une heure, le {{#time:l j xg Y|2026-09-23|fr}}.
* Si vous essayez de faire une modification ou de sauvegarder pendant cette période, vous verrez un message d’erreur. Nous espérons qu’aucune modification ne sera perdue durant ce temps, mais nous ne pouvons le garantir. Si vous voyez un message d’erreur, merci de patienter jusqu’au retour à la normale. Vous pourrez alors enregistrer votre modification. Nous vous conseillons cependant de faire une copie de votre modification avant, au cas où.
''Autres conséquences :''
* Les tâches de fond seront ralenties et certaines pourraient être stoppées. Les liens rouges ne seront pas mis à jour aussi vite que d’habitude. Si vous créez un article qui est déjà lié depuis une autre page, le lien rouge pourrait rester rouge plus longtemps que d’habitude. Certains scripts ayant un long temps d’exécution devront être stoppés.
* Le déploiement de code devrait se dérouler comme chaque semaine. Cependant, certains codes particuliers pourraient être gelés si l’opération le nécessitait.
* [[mw:Special:MyLanguage/GitLab|GitLab]] sera indisponible durant environ 90 minutes.
Ce projet pourra être reporté si nécessaire. Vous pouvez [[wikitech:Switch_Datacenter|consulter le calendrier sur wikitech.wikimedia.org]]. Tout changement sera annoncé dans le calendrier.
'''Merci de partager ces informations avec votre communauté.'''</div><section end="server-switch"/>
[[m:user:Trizek (WMF)|Trizek (WMF)]] 15 septembre 2026 à 14:58 (CEST)
<!-- Message envoyé par User:Trizek (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=30513874 -->
== Your feedback wanted: Remove ''(Language link added:) and similar'' edits from Special:RecentChanges ==
''(Apologies for posting in English. You can help by translating it)''
Hello, I’m Danny from [[:en:w:Wikimedia_Deutschland|Wikimedia Deutschland’s]] [[m:Wikidata_For_Wikimedia_Projects|Wikidata For Wikimedia Projects]] team.<br/>
We have been investigating and examining how Wikidata’s use in the [[Special:RecentChanges]] and [[Special:Watchlist]] pages can be made to be more useful, or less ‘noisy’ (fewer edits that have little value to the reader).
We are currently exploring to make a change in the volume and the type of Wikidata edits that appear in these pages, and '''we need your feedback''' to help us ensure this is a positive change for the Wikis and won’t disrupt your workflows.<br/>
Language links are also known as sitelinks or interlanguage links and describe when a Wikidata item is connected to a Wiki, adding it to the language dropdown tool for other connected Wikis.
[[File:Types of Lang Link changes.png|x350px|frame|A variety of "Language Link" edits as seen in the Recent Changes feed]]
The request for feedback is whether you support the removal of these language link edits from the Recent Changes and Watchlist feed, or would you oppose such a change and if so, why?
Please share you thoughts on the dedicated '''[[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|metawiki Talk page]]'''.
Further information about this change: [[m:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|m:Wikidata_For_Wikimedia_Projects/Hide_Language_Link_edits]]
Thank you for any participation or feedback you give, -- [[m:User:Danny_Benjafield_(WMDE)|User:Danny Benjafield (WMDE)]] 25 septembre 2026 à 13:46 (CEST)
<!-- Message envoyé par User:Danny Benjafield (WMDE)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_test_list&oldid=31080832 -->
== Actualités techniques n° 2026-39 ==
<section begin="technews-2026-W39"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/39|D’autres traductions]] sont disponibles.
'''En lumière cette semaine'''
* [[m:Special:MyLanguage/Tech/Server switch|Tous les wikis seront en lecture seule]] pendant quelques minutes le mercredi 23 septembre 2026 à [https://zonestamp.toolforge.org/1790172000 14 h 00 UTC]. Cette interruption est nécessaire pour effectuer des tests de sauvegarde dans le cadre de la bascule des serveurs du centre de données, [[wikitech:Special:MyLanguage/Deployments/Yearly calendar|qui a lieu deux fois par an]]. Pendant cette bascule, tout le trafic des sites web de Wikimédia est redirigé d'un centre de données principal vers le centre de données de secours afin de tester la disponibilité et d'éviter toute interruption de service, même en cas d'urgence. [https://phabricator.wikimedia.org/T433363]
'''Actualités pour la contribution'''
* L'équipe Growth a testé un nouvel avis post-édition destiné à [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#4. Encourage Temporary Accounts to Register|encourager les titulaires de comptes temporaires à créer un compte permanent]]. Ce nouvel avis a remplacé plusieurs boîtes de dialogue et une notification de bienvenue simultanée par un message unique mettant en avant les avantages de la création d'un compte. L'expérience a permis d'augmenter le taux de création de comptes permanents de 1,94 % à 3,66 %, soit une hausse relative de 87 %. Cette modification va désormais être déployée sur tous les wikis. [https://phabricator.wikimedia.org/T433551]
* Pour les contributeurs de Wikipédia utilisant la [[Special:Preferences#mw-prefsection-betafeatures|fonctionnalité bêta « Mode suggestion »]], il existe une [[Special:Preferences#mw-input-wpvisualeditor-editcheck-experimental|nouvelle préférence utilisateur à activer]] permettant d’afficher des contrôles d’édition et des suggestions « expérimentaux ». Ces types sont répertoriés sur la page [[Special:EditChecks#experimental-checks|Special:EditChecks]] et sont destinés aux premiers tests par les développeurs ainsi qu’à la collecte de retours d’expérience de la part d’utilisateurs expérimentés. Les administrateurs peuvent modifier les [[mw:Special:MyLanguage/Edit check/Configuration|détails de configuration]] de chaque suggestion comme d'habitude. Ils peuvent également [[mw:Special:MyLanguage/Help:Suggestion mode#Experimental Suggestions|utiliser cette fonctionnalité]] pour tester les idées de leur communauté concernant des contrôles et des suggestions créés localement. Les types expérimentaux bénéficieront d'un style visuel plus distinct dès la fin de cette semaine. [[mw:Talk:VisualEditor/Suggestion Mode|Vos retours sont les bienvenus]].
* Le [[m:CEE Technical Village Pump|CEE Technical Village Pump]] a été lancé afin d'offrir un espace dédié aux discussions techniques et à la collaboration entre les communautés Wikimédia d'Europe centrale et orientale.
* Un nouvel outil, [[mw:Special:MyLanguage/Language Onboarding and Development/Starter kit|Starter Kit]], est désormais disponible pour les communautés Wikipédia de langues nouvelles ou peu répandues. Il rassemble des tâches guidées, des outils et des ressources destinés à aider les communautés à se lancer, à suivre leurs progrès et à collaborer avec l'ensemble de la communauté Wikimédia. Le Starter Kit est hébergé sur Wikimedia Toolforge et est destiné aux Wikipédias des langues comptant moins de 50 000 articles. Pour en savoir plus sur le fonctionnement du Starter Kit et sur la manière dont les communautés l'utilisent, consultez [[diffblog:2026/09/18/introducing-starter-kit-for-new-and-small-language-wikipedias/|cet article du blog Diff]].
* L'équipe chargée de la croissance du nombre de lecteurs lance une nouvelle phase de test de l'[[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|expérience sur le carrousel d'images]]. Ce nouveau test utilisera trois nouvelles versions du design, mises à jour en fonction des commentaires de la communauté. L'équipe évaluera les résultats et déterminera avec les communautés s'il convient ou non de mettre en place cette fonctionnalité. L'expérience débutera la semaine du 28 septembre et se déroulera sur les Wikipédias en arabe, bengali, chinois, tchèque, anglais, farsi, français, allemand, indonésien, japonais, polonais, portugais, espagnol, suédois et vietnamien.
* Après avoir été déployée avec succès sur les Wikipédias en arabe, en bengali, en chinois, en tchèque, en anglais, en français, en indonésien et en vietnamien, la [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|fonctionnalité « Listes de lecture »]] développée par l'équipe chargée de l'expérience utilisateur sera accessible à tous les utilisateurs connectés sur l'ensemble des wikis Wikipédia à compter du 28 septembre 2026.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:28|la tâche soumise|les {{formatnum:28}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:28||s}} la semaine dernière]]. Par exemple, un problème lié au script de statut des mentors, qui mettait à jour de manière répétée le statut des mentors alors qu'aucun changement n'était intervenu, générant ainsi des entrées inutiles dans la section « Modifications récentes », a désormais été corrigé. [https://phabricator.wikimedia.org/T436659]
'''Actualités pour la contribution technique'''
* L'API de requête de liste <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi>, utilisée pour récupérer des informations spécifiques sur les filtres anti-abus actifs ou historiques configurés sur un wiki, a été mise à jour afin de prendre en charge le paramètre de configuration <bdi lang="zxx" dir="ltr"><code><nowiki>formatversion=2</nowiki></code></bdi>, ce qui entraîne des changements majeurs dans la réponse de l'API. Tous les utilisateurs qui gèrent des scripts utilisateur ou tout autre code doivent vérifier s'ils utilisent l'API de requête de liste <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi> et s'adapter à ces changements majeurs. [https://phabricator.wikimedia.org/T435828]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.21|MediaWiki]]
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/39|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W39"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 septembre 2026 à 13:56 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31085309 -->
== Actualités techniques n° 2026-40 ==
<section begin="technews-2026-W40"/><div class="plainlinks">
Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/40|D’autres traductions]] sont disponibles.
'''Actualités pour la contribution'''
* Les utilisateurs des skins par défaut sur le web pour ordinateur et mobile (respectivement Vector 2022 et Minerva) seront désormais informés si les suggestions de recherche affichées pendant la saisie dans la Recherche proviennent de redirections de pages. Auparavant, les utilisateurs pouvaient être confus lorsque les principales suggestions de recherche ne correspondaient pas exactement à ce qu'ils avaient tapé. La nouvelle notification de redirection précise que ces suggestions ne sont pas aléatoires, mais liées à la recherche. Les pages suggérées peuvent être des redirections vers les articles les plus pertinents recherchés. [https://phabricator.wikimedia.org/T303013]
* Les utilisateurs de Wikipédia déconnectés sur le web mobile verront bientôt un bouton de signet pour enregistrer des articles dans leurs [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|listes de lecture]] à la place du bouton Watchstar actuel. Lorsqu'on clique dessus, le bouton de signet invite les utilisateurs à se connecter ou à créer un compte pour enregistrer un article pour plus tard. Une expérience précédente a montré que l'icône de signet incitait quatre fois plus les utilisateurs déconnectés à créer un compte par rapport au bouton Watchstar. Ce changement fait partie des efforts pour encourager plus de lecteurs à devenir titulaires d'un compte et à utiliser des fonctionnalités qui les aident à sauvegarder et à revenir au contenu. [https://phabricator.wikimedia.org/T438769]
* Une expérience commencera le 29 septembre sur les Wikipédias en espagnol, arabe, anglais et français pour simplifier l'expérience que les utilisateurs rencontrent immédiatement après la création de leur compte. Le test permettra de voir si réduire les obstacles du sondage de bienvenue actuel aide les nouveaux utilisateurs à comprendre plus facilement ce qu'ils doivent faire ensuite. L'expérience se déroulera jusqu'à la fin du mois d'octobre. [https://phabricator.wikimedia.org/T430058]
* Le [[m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif sur les produits et la technologie]] a lancé un appel à la communauté pour suggérer des sujets qu'il devrait examiner et discuter avec la Wikimedia Foundation. Les éditeurs et contributeurs techniques sont invités à ajouter des sujets à [[m:Talk:Product and Technology Advisory Council|la page de discussion du conseil]].
* A la fin de cette semaine, il sera possible pour les communautés de configurer des [[mw:Special:MyLanguage/Help:Edit check|Vérifications et suggestions]] afin qu'elles soient [[mw:Special:MyLanguage/Edit check/Configuration#extraNamespaces|affichées dans des espaces de noms supplémentaires]], comme un espace de noms <bdi lang="zxx" dir="ltr"><code><nowiki>Draft:</nowiki></code></bdi>. [[mw:Talk:VisualEditor/Suggestion Mode|Vos retours sont les bienvenus]].
* Un bug dans la [[m:Special:GlobalWatchlist|Liste globale de surveillance]] empêche la page de se charger pour les utilisateurs qui ont des modifications non vues sur Wikimedia Commons. Les développeurs travaillent sur une solution. En attendant, les utilisateurs concernés peuvent suivre les solutions de contournement décrites dans la section d'aide [[mw:Special:MyLanguage/Extension:GlobalWatchlist#GlobalWatchlist temporarily broken for users with Wikimedia Commons|pertinente]].
* Les nouveaux mots magiques <bdi lang="zxx" dir="ltr"><code><nowiki>{{CATEGORYSORT:TIMESTAMP}}</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>{{CATEGORYSORT:RTIMESTAMP}}</nowiki></code></bdi> sont maintenant disponibles. Ils permettent de changer le tri par défaut des catégories pour qu'il soit basé sur la date et l'heure de la catégorisation. [https://phabricator.wikimedia.org/T433768]
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]]. Par exemple, le problème où [https://wikistats.wmcloud.org/ Wikistats] pour Wikipédia n'était pas accessible a maintenant été résolu. [https://phabricator.wikimedia.org/T435959]
'''Actualités pour la contribution technique'''
* Le basculement du centre des données, prévu le 23 septembre, a été reporté. [[diffblog:2025/03/12/hear-that-the-wikis-go-silent-twice-a-year/|L'exercice de l'équinoxe]] a révélé des problèmes de capacité, empêchant le transfert de tous les services vers l'autre centre des données. Le processus a été mis en pause pour se concentrer sur l'investigation de ce problème, afin de continuer à pouvoir servir nos utilisateurs de manière fiable. Un nouvel exercice sera planifié. En attendant, tout le trafic et les modifications continuent de fonctionner normalement.
* Le 23 septembre, une panne de courant a brièvement affecté tous les wikis, créant des problèmes intermittents en mode lecture et en mode édition. Cela a aussi affecté Gerrit. Cela n'a aucun lien avec le report du basculement du centre de données. [https://www.wikimediastatus.net/incidents/9fm19t8qykk8]
* [[mw:Special:MyLanguage/Help:Extension:Produnto|Produnto]] a été déployé sur mediawiki.org et [[phab:T421436|certaines wikis en langues indiquées]]. Nous voulons avoir vos retours sur ce produit expérimental. Produnto permet aux utilisateurs d'utiliser des modules Lua hébergés sur [[gitlab:repos/lua|GitLab de Wikimedia]]. Il simplifie le partage du code Lua entre les wikis. Au lieu de copier chaque module Lua individuellement d'un wiki à l'autre, les utilisateurs pourront facilement partager des packages entre tous les wikis.
* [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.22|MediaWiki]]
'''Rencontres et évènements'''
* Un [[m:Event:Cross-wiki Code Collaboration Workshop|atelier de collaboration sur le code entre wikis]] aura lieu en ligne le 2 octobre à 12h30 UTC, réunissant des contributeurs techniques d'Asie du Sud pour découvrir Produnto et commencer à le tester sur les Wikipédias en Hindi, Punjabi, Odia, Telugu et Malayalam. Produnto est un gestionnaire de paquets pour déployer des modules Lua hébergés sur GitLab vers les wikis Wikimedia, et a récemment été déployé sur certains wikis pilotes.
'''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]] • [[m:Special:MyLanguage/Tech/News/2026/40|Traduire]] • [[m:Tech|Obtenir de l’aide]] • [[m:Talk:Tech/News|Donner son avis]] • [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].''
</div><section end="technews-2026-W40"/>
<bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 28 septembre 2026 à 12:49 (CEST)
<!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31092225 -->
doky0rpqtjittcevr2t0ucqdyce2t09
Wikilivres:Prise de décision/Créer un espace lecteurs et un espace auteurs sur la page d'accueil
4
84486
773438
772159
2026-09-28T11:07:40Z
Fourmidable
92370
/* Votes */
773438
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire, et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de ::cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer. '''Pourriez vous cliquer sur l'image des livres dans la vitrine, pour voir si cela correspond à votre proposition.'''
::* '''Nouveautés et Projets En Construction Actif :''' [[Chanter les psaumes]]
::* '''Cette espace peut contenir une vingtaine de livres'''
::* '''[https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook''']'''
::* [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés. {{non signé|Lionel Scheepmans}}
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
# {{Pour}} (je m'ajoute à la PDD après la fin de la période de discussion). --[[Utilisateur:Fourmidable|Fourmidable]] ([[Discussion utilisateur:Fourmidable|discussion]]) 28 septembre 2026 à 13:07 (CEST)
== Décision ==
* Résultats : 3 {{pour}}, un {{neutre}} et un {{contre}} soit 60 % de {{pour}} par rapport au total des votes exprimés : ''la proposition est adoptée''. Quelques propositions de reformulations et de nuances ont été faites qui pourront être prise en compte. Cdlt, [[Utilisateur:VIGNERON|V<span style="font-size:75%">IGNERON</span>]] * [[Discussion Utilisateur:VIGNERON|<sup>discut.</sup>]] 13 septembre 2026 à 18:27 (CEST)
470ysnq3czycp7aw8xdvfmcnvh8f93j
Mathc initiation/007a
0
84526
773271
773265
2026-09-27T12:47:37Z
Xhungab
23827
773271
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
{{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}}
{{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}}
==Étudions quelques cas :==
{{Partie{{{type|}}}|[[Mathc initiation/007d|* Z(a^n x(n)) = X(z/a)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007c|* Z[n x(n)] = -z X'(z)]]}}
{{AutoCat}}
fvulm8jxhamipc8k1kyetjt00bdge2x
773316
773271
2026-09-27T16:53:25Z
Xhungab
23827
773316
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
{{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}}
{{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}}
==Étudions quelques cas :==
{{Partie{{{type|}}}|[[Mathc initiation/007d|* Z(a^n x(n)) = X(z/a)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007c|* Z[n x(n)] = -z X'(z) I]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007e|* Z[n x(n)] = -z X'(z) II]]}}
{{AutoCat}}
gknp0l3achfs5a0lyz9tzr9bpfukjwm
773324
773316
2026-09-27T17:04:17Z
Xhungab
23827
773324
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
{{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}}
{{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}}
==Étudions quelques cas :==
{{Partie{{{type|}}}|[[Mathc initiation/007d|* Z(a^n x(n)) = X(z/a)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007c|* Z[n x(n)] = -z X'(z) ... (I)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007e|* Z[n x(n)] = -z X'(z) ... (II)]]}}
{{AutoCat}}
ip9c1sgkol1hhmoi68l6q3qo6sz4nf5
773422
773324
2026-09-28T08:36:42Z
Xhungab
23827
773422
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
{{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}}
{{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}}
==Étudions quelques cas :==
{{Partie{{{type|}}}|[[Mathc initiation/007d|* Z(a^n x(n)) = X(z/a)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007c|* Z[n x(n)] = -z X'(z) ... (I)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007e|* Z[n x(n)] = -z X'(z) ... (II)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007f|* Z[n^2 x(n)] = -z X"(z) ... (I)]]}}
{{AutoCat}}
q8l77a7w6jc35qek4jw4dwyof5nwxah
773424
773422
2026-09-28T08:58:58Z
Xhungab
23827
773424
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
{{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}}
{{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}}
==Étudions quelques cas :==
{{Partie{{{type|}}}|[[Mathc initiation/007d|* X(z) = z/(z-2); X(z) =z/(z+2)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007c|* X(z) = z/(z-3)^2]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007e|* X(z) = z/(z+2)^2]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007f|* X(z) = z/(z-3)^3]]}}
{{AutoCat}}
i9vmucve1i8j057vc405b9s5rqihcmi
773427
773424
2026-09-28T10:03:02Z
Xhungab
23827
773427
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
{{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}}
{{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}}
==Étudions quelques cas :==
{{Partie{{{type|}}}|[[Mathc initiation/007d|* X(z) = z/(z-2); X(z) =z/(z+2)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007c|* X(z) = z/(z-3)^2]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007e|* X(z) = z/(z+2)^2]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007f|* X(z) = z/(z-3)^3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/007g|* X(z) = z/(z+3)^3]]}}
{{AutoCat}}
cz5d10ee3comqpi84yvrpxqgs3fvq9o
Mathc initiation/007c
0
84528
773275
773268
2026-09-27T12:54:07Z
Xhungab
23827
773275
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a|Sommaire]]
* [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']]
* [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']]
* [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']]
* [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']]
* [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']]
==X(z) = z/(z-3)^2==
On veut retrouver x[n]
X(z) = z/(z-3)^2
x[n] = n 3^(n-1) u[n]
Remarque 1 : Remarque 2 :
z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2
'''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2'''
[[Mathc initiation/a588|-z X'(z) = Z(n x(n))]]
==X(z) = 3 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 3 z/(z-3)^2
x[n] = 3 n 3^(n-1) u[n]
x[n] = n 3^n u[n]
== X(z) = 9 z/(z-3)^2 ==
On veut retrouver x[n]
X(z) = 9 z/(z-3)^2
x[n] = 9 n 3^(n-1) u[n]
x[n] = 3 n 3^n u[n]
==X(z) = 27 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 27 z/(z-3)^2
x[n] = 27 n 3^(n-1) u[n]
x[n] = 9 n 3^n u[n]
==X(z) = 6 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 6 z/(z-3)^2
x[n] = 6 n 3^(n-1) u[n]
x[n] = 2 n 3^n u[n]
{{AutoCat}}
rsp2p7aeinc4an2gd0tliouerl15ilj
773322
773275
2026-09-27T17:02:02Z
Xhungab
23827
773322
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']]
* [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']]
* [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']]
* [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']]
* [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']]
==X(z) = z/(z-3)^2==
On veut retrouver x[n]
X(z) = z/(z-3)^2
x[n] = n 3^(n-1) u[n]
Remarque 1 : Remarque 2 :
z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2
'''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2'''
[[Mathc initiation/a588|-z X'(z) = Z(n x(n))]]
==X(z) = 3 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 3 z/(z-3)^2
x[n] = 3 n 3^(n-1) u[n]
x[n] = n 3^n u[n]
== X(z) = 9 z/(z-3)^2 ==
On veut retrouver x[n]
X(z) = 9 z/(z-3)^2
x[n] = 9 n 3^(n-1) u[n]
x[n] = 3 n 3^n u[n]
==X(z) = 27 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 27 z/(z-3)^2
x[n] = 27 n 3^(n-1) u[n]
x[n] = 9 n 3^n u[n]
==X(z) = 6 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 6 z/(z-3)^2
x[n] = 6 n 3^(n-1) u[n]
x[n] = 2 n 3^n u[n]
{{AutoCat}}
t777ptfjwnj1nzta6w4u1k63nqcsuhe
773425
773322
2026-09-28T09:16:28Z
Xhungab
23827
773425
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']]
* [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']]
* [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']]
* [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']]
* [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']]
==X(z) = z/(z-3)^2==
On veut retrouver x[n]
X(z) = z/(z-3)^2
x[n] = n 3^(n-1) u[n]
Remarque 1 : Remarque 2 :
z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2
'''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2'''
[[Mathc initiation/a588|-z X'(z) = Z(n x(n))]]
==X(z) = 3 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 3 z/(z-3)^2
x[n] = 3 n 3^(n-1) u[n]
x[n] = n 3^n u[n]
== X(z) = 9 z/(z-3)^2 ==
On veut retrouver x[n]
X(z) = 9 z/(z-3)^2
x[n] = 9 n 3^(n-1) u[n]
x[n] = 3 n 3^n u[n]
x[n] = n 3^(n+1) u[n]
==X(z) = 27 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 27 z/(z-3)^2
x[n] = 27 n 3^(n-1) u[n]
x[n] = 9 n 3^n u[n]
x[n] = n 3^(n+2) u[n]
==X(z) = 6 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 6 z/(z-3)^2
x[n] = 6 n 3^(n-1) u[n]
x[n] = 2 n 3^n u[n]
{{AutoCat}}
k297aubg99ars8l0pfktno7cwmk6z4r
773432
773425
2026-09-28T10:41:32Z
Xhungab
23827
773432
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']]
* [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']]
* [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']]
* [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']]
* [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']]
==X(z) = z/(z-3)^2==
On veut retrouver x[n]
X(z) = z/(z-3)^2
x[n] = n 3^(n-1) u[n]
Remarque 1 : Remarque 2 :
z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2
'''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2'''
[[Mathc initiation/a588|-z X'(z) = Z(n x(n))]]
==X(z) = 3 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 3 z/(z-3)^2
x[n] = 3 n 3^(n-1) u[n]
x[n] = n 3^n u[n]
== X(z) = 9 z/(z-3)^2 ==
On veut retrouver x[n]
X(z) = 9 z/(z-3)^2
x[n] = 9 n 3^(n-1) u[n]
x[n] = 3 n 3^n u[n]
x[n] = n 3^(n+1) u[n]
==X(z) = 27 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 27 z/(z-3)^2
x[n] = 27 n 3^(n-1) u[n]
x[n] = 9 n 3^n u[n]
x[n] = n 3^(n+2) u[n]
==X(z) = 6 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 6 z/(z-3)^2
x[n] = 6 n 3^(n-1) u[n]
x[n] = 2 n 3^n u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* n 3^(n-1)
* n 3^n
* n 3^(n+1)
* n 3^(n+2)
* 2 n 3^n
{{AutoCat}}
3ne4zrsf3imlhlvm9blqu39gnjq5qlr
773435
773432
2026-09-28T10:44:52Z
Xhungab
23827
773435
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']]
* [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']]
* [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']]
* [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']]
* [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']]
==X(z) = z/(z-3)^2==
On veut retrouver x[n]
X(z) = z/(z-3)^2
x[n] = n 3^(n-1) u[n]
Remarque 1 : Remarque 2 :
z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2
'''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2'''
[[Mathc initiation/a588|-z X'(z) = Z(n x(n))]]
==X(z) = 3 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 3 z/(z-3)^2
x[n] = 3 n 3^(n-1) u[n]
x[n] = n 3^n u[n]
== X(z) = 9 z/(z-3)^2 ==
On veut retrouver x[n]
X(z) = 9 z/(z-3)^2
x[n] = 9 n 3^(n-1) u[n]
x[n] = 3 n 3^n u[n]
x[n] = n 3^(n+1) u[n]
==X(z) = 27 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 27 z/(z-3)^2
x[n] = 27 n 3^(n-1) u[n]
x[n] = 9 n 3^n u[n]
x[n] = n 3^(n+2) u[n]
==X(z) = 6 z/(z-3)^2==
On veut retrouver x[n]
X(z) = 6 z/(z-3)^2
x[n] = 6 n 3^(n-1) u[n]
x[n] = 2 n 3^n u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* n 3^(n-1)
* n 3^n
* n 3^(n+1)
* n 3^(n+2)
* 2 n 3^n
{{AutoCat}}
2cz6wy9j9lhdsvose95re2jpdagp9ew
Mathc initiation/007d
0
84529
773272
2026-09-27T12:50:09Z
Xhungab
23827
news
773272
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
* [[#X(z) = z/(z-2)| '''X(z) = z/(z-2)''']]
* [[#X(z) =z/(z+2)| '''X(z) =z/(z+2)''']]
==X(z) = z/(z-2)==
On veut retrouver x[n]
X(z) = z/(z-2)
x[n] = 2^(n) u[n]
Remarque : z/(z-a) <-> a^(n) u[n]
==X(z) =z/(z+2)==
On veut retrouver x[n]
X(z) = z/(z+2)
X(z) = z/(z-(-2))
x[n] = (-2)^(n) u[n]
Remarque : z/(z+a) = z/(z-(-a)) <-> (-a)^(n) u[n]
{{AutoCat}}
5mq9a13l0754niod8weovjsbwbuj7ud
773273
773272
2026-09-27T12:52:17Z
Xhungab
23827
773273
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
* [[#X(z) = z/(z-2)| '''X(z) = z/(z-2)''']]
* [[#X(z) =z/(z+2)| '''X(z) =z/(z+2)''']]
==X(z) = z/(z-2)==
On veut retrouver x[n]
X(z) = z/(z-2)
x[n] = 2^(n) u[n]
Remarque : z/(z-a) <-> [[Mathc initiation/a585|a^(n) u[n]]]
==X(z) =z/(z+2)==
On veut retrouver x[n]
X(z) = z/(z+2)
X(z) = z/(z-(-2))
x[n] = (-2)^(n) u[n]
Remarque : z/(z+a) = z/(z-(-a)) <-> (-a)^(n) u[n]
{{AutoCat}}
0emkbzf6f15izf59ba9apl9vr0f3plk
773274
773273
2026-09-27T12:53:46Z
Xhungab
23827
773274
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a|Sommaire]]
* [[#X(z) = z/(z-2)| '''X(z) = z/(z-2)''']]
* [[#X(z) =z/(z+2)| '''X(z) =z/(z+2)''']]
==X(z) = z/(z-2)==
On veut retrouver x[n]
X(z) = z/(z-2)
x[n] = 2^(n) u[n]
Remarque : z/(z-a) <-> [[Mathc initiation/a585|a^(n) u[n]]]
==X(z) =z/(z+2)==
On veut retrouver x[n]
X(z) = z/(z+2)
X(z) = z/(z-(-2))
x[n] = (-2)^(n) u[n]
Remarque : z/(z+a) = z/(z-(-a)) <-> (-a)^(n) u[n]
{{AutoCat}}
kb1iwwm3tx21vf99pv2r67es8c48qpk
773321
773274
2026-09-27T17:01:28Z
Xhungab
23827
773321
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-2)| '''X(z) = z/(z-2)''']]
* [[#X(z) =z/(z+2)| '''X(z) =z/(z+2)''']]
==X(z) = z/(z-2)==
On veut retrouver x[n]
X(z) = z/(z-2)
x[n] = 2^(n) u[n]
Remarque : z/(z-a) <-> [[Mathc initiation/a585|a^(n) u[n]]]
==X(z) =z/(z+2)==
On veut retrouver x[n]
X(z) = z/(z+2)
X(z) = z/(z-(-2))
x[n] = (-2)^(n) u[n]
Remarque : z/(z+a) = z/(z-(-a)) <-> (-a)^(n) u[n]
{{AutoCat}}
tiuqt6gqf0w1ootmjz0xgtgreya1gio
773433
773321
2026-09-28T10:44:06Z
Xhungab
23827
773433
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-2)| '''X(z) = z/(z-2)''']]
* [[#X(z) =z/(z+2)| '''X(z) =z/(z+2)''']]
==X(z) = z/(z-2)==
On veut retrouver x[n]
X(z) = z/(z-2)
x[n] = 2^(n) u[n]
Remarque : z/(z-a) <-> [[Mathc initiation/a585|a^(n) u[n]]]
==X(z) =z/(z+2)==
On veut retrouver x[n]
X(z) = z/(z+2)
X(z) = z/(z-(-2))
x[n] = (-2)^(n) u[n]
Remarque : z/(z+a) = z/(z-(-a)) <-> (-a)^(n) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* 2^(n)
* (-2)^(n)
{{AutoCat}}
pyujve8jy4dcwh35ew5ea0she33bd6v
773434
773433
2026-09-28T10:44:22Z
Xhungab
23827
773434
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-2)| '''X(z) = z/(z-2)''']]
* [[#X(z) =z/(z+2)| '''X(z) =z/(z+2)''']]
==X(z) = z/(z-2)==
On veut retrouver x[n]
X(z) = z/(z-2)
x[n] = 2^(n) u[n]
Remarque : z/(z-a) <-> [[Mathc initiation/a585|a^(n) u[n]]]
==X(z) =z/(z+2)==
On veut retrouver x[n]
X(z) = z/(z+2)
X(z) = z/(z-(-2))
x[n] = (-2)^(n) u[n]
Remarque : z/(z+a) = z/(z-(-a)) <-> (-a)^(n) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* 2^(n)
* (-2)^(n)
{{AutoCat}}
6sic1rgbdff1c8tdoceonpundw3ha6p
Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : basse consommation
0
84530
773295
2026-09-27T15:55:47Z
Mewtow
31375
Page créée avec « Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais... »
773295
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Pour cela, elles font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, le processeur Atom n'utilisait pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
4tvy7wijl59sw1o127dzqj6eamwoc13
773299
773295
2026-09-27T15:57:07Z
Mewtow
31375
/* Le système d'exceptions flottantes de l'Atom */
773299
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Pour cela, elles font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, le processeur Atom n'utilisait pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
d9ko580w20j5dnfkjzmtobkib7ergph
773302
773299
2026-09-27T15:59:36Z
Mewtow
31375
773302
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, le processeur Atom n'utilisait pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
8aux4mqfpob7ripb4e6ns1omeb2v7jp
773303
773302
2026-09-27T16:09:43Z
Mewtow
31375
/* Les microarchitectures basse consommation */
773303
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi des microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
7edpsfmogt503wua4iefsk5q81z8zpm
773306
773303
2026-09-27T16:15:22Z
Mewtow
31375
/* Les microarchitectures basse consommation */
773306
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi des microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
rck8jeejdhuxzyaxgsg67y8b1tdp925
773308
773306
2026-09-27T16:18:23Z
Mewtow
31375
/* Le système d'exceptions flottantes de l'Atom */
773308
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi des microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
La différence entre les microarchitectures P et E tient dans le système d'exécution dans le désordre. Le ROB, les fenêtres d'instructions, les différentes files de µops et caches : tout est plus petit sur les coeurs E.
De plus, les coeurs E utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeur sE. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
48bxivvidy13pfuwrns50gnjlog90oy
773309
773308
2026-09-27T16:19:57Z
Mewtow
31375
/* Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont */
773309
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi des microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
af8592cdv2vbopgco0xmyn4vfglha0k
773310
773309
2026-09-27T16:21:33Z
Mewtow
31375
/* Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont */
773310
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi des microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Par exemple, voici ce que donne la microarchitecture '''Gracemont'''. Elle peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
Une optimisation assez fréquente est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeurs E. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
02eg91cujb4hvusli43wg4lbxs5rj8y
773311
773310
2026-09-27T16:24:27Z
Mewtow
31375
/* Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont */
773311
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi des microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie. Par exemple, ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeurs E. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
93qiubzmba3sd6hkcyyznjzr6cs7zu4
773312
773311
2026-09-27T16:25:59Z
Mewtow
31375
/* Les microarchitectures basse consommation d'Intel */
773312
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie. Par exemple, ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont, pour coeurs E. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction. Les performances mémoire et flottantes sont donc un peu réduites.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
refl86i35t1uoyu728w30isuuyri2h7
773313
773312
2026-09-27T16:33:09Z
Mewtow
31375
/* Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont */
773313
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. L'unité de prédiction de branchement est cependant moins puissante que pour un coeur haute performante "équivalent", sorti la même année.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie. Par exemple, ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis au moins Tremont, utilisent un ''front-edn'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". A savoir qu'ils chargent deux à trois blocs de trois instructions, chaque bloc étant séparé par des branchements.
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
rj4l2x7yh6ffvzrbklzdau319v1jie5
773314
773313
2026-09-27T16:36:43Z
Mewtow
31375
/* Les microarchitectures basse consommation */
773314
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les plus récentes sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore.
Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie. Par exemple, ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis au moins Tremont, utilisent un ''front-edn'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". A savoir qu'ils chargent deux à trois blocs de trois instructions, chaque bloc étant séparé par des branchements.
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
dzxd2l3d5msc75jq9ugriy01ru5k1ef
773315
773314
2026-09-27T16:51:14Z
Mewtow
31375
/* Les microarchitectures Silvermont, Goldmont, Tremont et Gracemont */
773315
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et ses successeurs==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres.
===Les microarchitectures Tremont, Gracemont et Crestmont===
Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec. En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
j0u32d5fl3vuuqeruu7v7vc2pgb8qnd
773318
773315
2026-09-27T16:59:11Z
Mewtow
31375
/* Les microarchitectures Tremont, Gracemont et Crestmont */
773318
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et ses successeurs==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres.
===Les microarchitectures Tremont, Gracemont et Crestmont===
Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec. En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
6jd8mjg9jikh476l41i2rpmsmxigdch
773320
773318
2026-09-27T17:00:26Z
Mewtow
31375
/* Les microarchitectures Silvermont et ses successeurs */
773320
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et ses successeurs==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec. En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
f6jutlqx95ug4q9bmrttx01vql6ihey
773325
773320
2026-09-27T17:29:17Z
Mewtow
31375
/* Le front-end des microarchitectures récentes d'Intel */
773325
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et ses successeurs==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
sydzzeqbzcf8us70djxqgemgmo80dhr
773326
773325
2026-09-27T17:57:30Z
Mewtow
31375
/* Les microarchitectures Silvermont et ses successeurs */
773326
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
!
| ||
|-
!
| ||
|-
!
| ||
|-
!
| ||
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
9yrwjpucs4nc0olvl0ghqbqrbiip73r
773327
773326
2026-09-27T17:57:48Z
Mewtow
31375
/* Les microarchitectures Silvermont et Goldmont */
773327
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
!
| ||
|-
!
| ||
|-
!
| ||
|-
!
| ||
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
8imilt0ofs8xurwjhpoy6m6yl50jrvm
773328
773327
2026-09-27T18:18:13Z
Mewtow
31375
/* Le front-end de Silvermont et Goldmont */
773328
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || ALU SIMD entière (128bits) || ALU SIMD entière (128bits) ||
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations associées passent à 10 entrées (sauf pour les µops mémoire, qui restent à 8). L'ALU pour les branchements recoit son propre port d'émission et sa réservation de station associée. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher ca échant. Le plus tot ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! PStation de réservation 8
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
6mj5e6wktj2g6wsdjogv2lw4ht4y4g3
773329
773328
2026-09-27T18:37:22Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773329
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| || || || || ||
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || ALU SIMD entière (128bits) || ALU SIMD entière (128bits) ||
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations associées passent à 10 entrées (sauf pour les µops mémoire, qui restent à 8). L'ALU pour les branchements recoit son propre port d'émission et sa réservation de station associée. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher ca échant. Le plus tot ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! PStation de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
p30jgp678pbwkyeqno47xfj9r5s5tlk
773330
773329
2026-09-27T18:38:40Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773330
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || ALU SIMD entière (128bits) || ALU SIMD entière (128bits) ||
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations associées passent à 10 entrées (sauf pour les µops mémoire, qui restent à 8). L'ALU pour les branchements recoit son propre port d'émission et sa réservation de station associée. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher ca échant. Le plus tot ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
4efp8xklqj96m4jmp9eu6616nq9mkrp
773331
773330
2026-09-27T18:39:43Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773331
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. L'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher ca échant. Le plus tot ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
b2ira5ps4ek461w0yk4g33fjxksv7sm
773332
773331
2026-09-27T18:40:36Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773332
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher ca échant. Le plus tot ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
jozkr88i0n9bfmz0upj214ax2x12dfd
773333
773332
2026-09-27T18:42:38Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773333
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
dr560arptldazqob33kdp599cvhe2q8
773334
773333
2026-09-27T18:52:12Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773334
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de prots du banc de registre, en réapartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Vous remarquerez une constante sur ces deux architectures : ils utilisent des stations de réservations décentralisées. Presque chaque unité a sa propre station de réservation, à l'exception du multiplieur et du ''barrel shifter''. La raison est que faire ainsi réduit les performance,s mais entraine un gain en consommation d'électricité non négligeable. La perte de performance vient du fait que certaines stations de réservation peuvent être inutilisées, par exemple cas d’absence d'opérations flottantes. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe.
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
orogrjhvksbni1tonh9489upoaarqog
773335
773334
2026-09-27T18:55:04Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773335
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de prots du banc de registre, en réapartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Vous remarquerez une constante sur ces deux architectures : ils utilisent des stations de réservations décentralisées. Presque chaque unité a sa propre station de réservation, à l'exception du multiplieur et du ''barrel shifter''. La raison est que faire ainsi réduit les performance,s mais entraine un gain en consommation d'électricité non négligeable. La perte de performance vient du fait que certaines stations de réservation peuvent être inutilisées, par exemple cas d’absence d'opérations flottantes. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
7gfyfezpj8c9fx5tbwtamqe6z0i4rwo
773343
773335
2026-09-27T19:53:58Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773343
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de prots du banc de registre, en réapartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont et Goldmont se limitent )à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port. De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, particulièrement simple, à savoir un port de lecture et un d'écriture. Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM), ce qui est un énorme avantge en termes d'économie de circuits et de consommation énergétique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Vous remarquerez une constante sur ces deux architectures : ils utilisent des stations de réservations décentralisées. Presque chaque unité a sa propre station de réservation, à l'exception du multiplieur et du ''barrel shifter''. La raison est que faire ainsi réduit les performance,s mais entraine un gain en consommation d'électricité non négligeable. La perte de performance vient du fait que certaines stations de réservation peuvent être inutilisées, par exemple cas d’absence d'opérations flottantes. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
ffd12x2ot03hfyqwcotwziymf3wg37j
773344
773343
2026-09-27T20:00:21Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773344
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures Silvermont et Goldmont==
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Vous remarquerez une constante sur ces deux architectures : ils utilisent des stations de réservations décentralisées. Presque chaque unité a sa propre station de réservation, à l'exception du multiplieur et du ''barrel shifter''. La raison est que faire ainsi réduit les performances, mais entraine un gain en consommation d'électricité non négligeable. La perte de performance vient du fait que certaines stations de réservation peuvent être inutilisées, par exemple cas d’absence d'opérations flottantes. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
De plus, Silvermont et Goldmont utilisent une file de µops pour l'unité mémoire, alors que les autres unités ont bien une station de réservation dédiée. Là encore, cela entraine une économie de circuits et une baisse de la consommation d’électricité. Le cout en performance est important, mais
==Les microarchitectures Tremont, Gracemont et Crestmont==
Les microarchitectures Silvermont, Goldmont, Tremont, Gracemont et Crestmont font suite à l'architecture Bonnell. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cette superscalarité large vient avec des optimisations pour réduire le cout en circuit/énergie.
===Le ''front-end'' des microarchitectures récentes d'Intel===
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
En remplacement, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
===Le reste de ces microarchitectures===
Une autre particularité est que ces microarchitectures utilisent des files d'instructions pour les opérations mémoire et flottantes, au lieu de fenêtres d'instruction. Les performances mémoire et flottantes sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
d03k72lcv1jjwtp56hw90qwtwz0k41f
773345
773344
2026-09-27T20:12:30Z
Mewtow
31375
773345
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où presque chaque unité a sa propre station de réservation, à l'exception des unités mémoire. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable. La perte de performance vient du fait que certaines stations de réservation peuvent être inutilisées. Par exemple, la station de réservation flottante est inutilisée en cas d’absence d'opérations flottantes. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
Une seconde optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
De plus, Silvermont et Goldmont utilisent une file de µops pour l'unité mémoire, alors que les autres unités ont bien une station de réservation dédiée. Là encore, cela entraine une économie de circuits et une baisse de la consommation d’électricité. Le cout en performance est important, mais
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
37uqczri9hxiqfv7hxhq73hs378ya8i
773346
773345
2026-09-27T20:13:04Z
Mewtow
31375
/* Les microarchitectures de Silvermont à Crestmont */
773346
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où presque chaque unité a sa propre station de réservation, à l'exception des unités mémoire. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable. La perte de performance vient du fait que certaines stations de réservation peuvent être inutilisées. Par exemple, la station de réservation flottante est inutilisée en cas d’absence d'opérations flottantes. Le gain en matière d'énergie vient de l'économie de circuits, qui est assez drastique.
Je rappelle qu'une station de réservation a besoin, pour N instructions, d'environ N^2 circuits (des comparateurs, notamment). L'organisation de Silvermont utilise 38 entrées en tout avec une moyenne proche de 8 par entrée, ce qu'il faut comparer à une station de réservation centralisée de 38 entrées. Entre un cout proportionnel à 38² et 8^² * 4, le choix est clair : la seconde solution est plus économe. D'où l'utilisation de cette solution pour une microarchitecture basse consommation.
Une seconde optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
De plus, Silvermont et Goldmont utilisent une file de µops pour l'unité mémoire, alors que les autres unités ont bien une station de réservation dédiée. Là encore, cela entraine une économie de circuits et une baisse de la consommation d’électricité. Le cout en performance est important, mais
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
ef2rmt8rhfuha9r2doqn3ic39qvbt7h
773352
773346
2026-09-27T20:39:55Z
Mewtow
31375
/* Les microarchitectures de Silvermont à Crestmont */
773352
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Le ''back-end'' de Silvermont et Goldmont===
Pour ce qui est des ''back-end'', il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
La microarchitecture Goldmont était une microarchitecture superscalaire qui était intermédiaire entre la triple émission et la quadruplke émission. Elle pouvait charger et décoder trois instructions par cycles, mais émettre 4 µops par cycle. La raison à cela est qu'une instruction x86 peut être décodée en plusieurs µops, avoir une quatrième µops en rab ne fait pas de mal.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
bq6aodtg2n99w4fkvgejbza9s637go2
773356
773352
2026-09-27T20:56:40Z
Mewtow
31375
/* Le back-end de Silvermont et Goldmont */
773356
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l'untié de renommage. Nous verrons pourquoi plus tard.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
rtpidievx6u7ippopjehubgr4pvu7uy
773357
773356
2026-09-27T20:58:13Z
Mewtow
31375
/* Les unités fonctionnelles et l'exécution dans le désordre */
773357
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l'untié de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. l'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
9hh6io1hrhimen1ql0ax5lbj9v4rrxq
773361
773357
2026-09-27T21:12:24Z
Mewtow
31375
/* Les unités fonctionnelles et l'exécution dans le désordre */
773361
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
4nj7ls7a4bg12wkru5qukzh5c0ua6ds
773366
773361
2026-09-27T21:25:46Z
Mewtow
31375
/* Les unités fonctionnelles et l'exécution dans le désordre */
773366
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont rajoute une ALU entière, les stations de réservations augmentent en taille. Goldmont fusionne les station de réservation pour les unités flottantes. Et à l'opposé, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
le123dghw97pz158qbmrsv6yznlft0x
773367
773366
2026-09-27T21:26:45Z
Mewtow
31375
/* Les unités fonctionnelles et l'exécution dans le désordre */
773367
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
===Le ''front-end'' des microarchitectures récentes d'Intel===
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
izhe46u4l8fv97y3o16umbjqp6vo09p
773368
773367
2026-09-27T21:30:30Z
Mewtow
31375
/* Le front-end des microarchitectures récentes d'Intel */
773368
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
===Le ''front-end'' de Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
pbogt8jxsck4cqva7iu66x8ou4zjrnt
773369
773368
2026-09-27T21:30:53Z
Mewtow
31375
/* Le front-end de Silvermont et Goldmont */
773369
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|-
! Unité de renommage
| || 4µops par cycle + zero idioms
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
4ryg5yjn2kz5fhdx4miv6huzbnq21tc
773370
773369
2026-09-27T21:31:03Z
Mewtow
31375
/* Le front-end : un changement après Silvermont et Goldmont */
773370
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
syh2ps4qdbpz3y6omnh03wsvn0zzrco
773371
773370
2026-09-27T21:34:09Z
Mewtow
31375
/* L'unité mémoire des CPU Intel basse consommation */
773371
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine. Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire et la FPU des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
Les opérations flottantes étant assez longues, Gracemont utilise le même système pour les opérations flottantes. Une file de µops précède deux stations de réservation. La première accumule des opérations flottantes, destinées à être exécutées dans la FPU (de type SIMD). La file de µops fait 56 entrées, alors que la station de réservation en a 35. Une seconde station de réservation de 18 entrées met en attente les écritures flottantes, à savoir les écritures qui écrivent un flottant simple ou double précision en mémoire RAM.
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
pi5ze6hz55b9xn9k3f1b6c0xs3cgl2b
773372
773371
2026-09-27T21:36:48Z
Mewtow
31375
/* Les microarchitectures de Silvermont à Crestmont */
773372
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire et la FPU des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
Les opérations flottantes étant assez longues, Gracemont utilise le même système pour les opérations flottantes. Une file de µops précède deux stations de réservation. La première accumule des opérations flottantes, destinées à être exécutées dans la FPU (de type SIMD). La file de µops fait 56 entrées, alors que la station de réservation en a 35. Une seconde station de réservation de 18 entrées met en attente les écritures flottantes, à savoir les écritures qui écrivent un flottant simple ou double précision en mémoire RAM.
Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
fr3mc34999dn4zel0he7n7cssx3mbm5
773376
773372
2026-09-27T22:07:01Z
Mewtow
31375
/* Les microarchitectures basse consommation */
773376
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
===Les sources d'économie dans le cas général===
Les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
Un autre détail est que les microarchitecture basse consommation ne mettent pas le paquet sur les instructions SIMD. Elles se contentent d'utiliser des unités de calcul 128 bits. Pour l'AVX, qui utilise des registres de 256 bits, ,voire 512, les calculs sont fait en deux fois. La conséquence est qu'une instruction SIMD de type SSE ou AVX, est décodée en deux micro-opérations 128 bits.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Les différentes microarchitectures sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 48 octets par cycle || 9 instructions par cycle || 2 µops par cycle
|}
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire et la FPU des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
Les opérations flottantes étant assez longues, Gracemont utilise le même système pour les opérations flottantes. Une file de µops précède deux stations de réservation. La première accumule des opérations flottantes, destinées à être exécutées dans la FPU (de type SIMD). La file de µops fait 56 entrées, alors que la station de réservation en a 35. Une seconde station de réservation de 18 entrées met en attente les écritures flottantes, à savoir les écritures qui écrivent un flottant simple ou double précision en mémoire RAM.
Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
f6cs9e987x03itxo6tlq9zlhaby5etq
773414
773376
2026-09-28T01:17:07Z
Mewtow
31375
/* Les microarchitectures de Silvermont à Crestmont */
773414
wikitext
text/x-wiki
Le chapitre précédent étudiait les processeurs x86 destinés aux PC fixe, au ''gaming'', aux serveurs, bref : à ce tout ce qui demande de la performance avant tout. De telles processeurs sont appelés des processeurs haute performance. Mais divers processeurs x86 visent eux, non pas la performance, mais surtout une consommation d'énergie réduite. Ils sont destinés aux PC portables, essentiellement. Cela ne veut pas dire qu'ils ne sont pas performants, mais qu'ils font un compromis différent : moins de performance, moins de consommation.
==Les microarchitectures basse consommation==
Et qui dit basse consommation dit souvent : microarchitectures adaptées. Alors certes, certains modèles basse consommation utilisent les microarchitectures du chapitre précédent, et retirent des coeurs ou du cache. C'est une possibilité, mais elle n'est pas systématique. De nombreux processeurs basse consommation utilisent une microarchitecture adaptée, prévue pour consommer moins. Les microarchitectures de ce genre seront appelées, dans ce chapitre, des '''microarchitectures basse consommation'''.
===Les sources d'économie dans le cas général===
Les microarchitectures basse consommation font des économies à divers endroits. Des économies sur l'exécution dans le désordre : ROB plus petit, fenêtres d'instructions plus petites, ''store queue'' plus petites. Des économies sur les unités : moins d'ALU, moins de FPU, unités SIMD plus petites, moins d'unité mémoire, une seule unité de branchement, etc. Le banc de registre peut avoir moins de ports, ce qui entraine l'apparition de contraintes d’appariement. Et bien d'autres.
Les toutes premières microarchitectures basse consommation se débarrassaient de l’exécution dans le désordre. Les processeurs basse consommation ARM faisaient cela, de même que les processeurs MIPS ou autres destinés à l'embarqué. Chez Intel, les premiers processeurs Atom d'Intel n'utilisaient pas l'exécution dans le désordre, ce qui fait qu'il aura droit à sa propre section dédiée. Mais la loi de Moore a changé la donne avec le temps. Même pour de la basse consommation, l'exécution dans le désordre est utilisée. Le gain en performance est tellement important que le ratio performance/consommation est amélioré. Quitte à gagner 50% de performances avec une consommation augmentée de 30%, autant en profiter, quitte à faire des économies ailleurs.
Niveau prédiction de branchements, ils tendent à ne pas faire trop d'économie. La simple présence d'un pipeline rend la prédiction de branchement nécessaire, et l'exécution dans le désordre la rend encore plus importante. De fait, la prédiction de branchement évite du 'gaspillage", ce qui fait qu'elle améliore aussi bien les performances que la consommation d'énergie, si elle est correctement calibrée. Annuler des instructions exécutées à tord, c'est avant tout gaspiller de l'électricité pour rien.
===Les microarchitectures basse consommation d'Intel===
La première microarchitecture basse consommation d'Intel était celle des processeurs Atom, une gamme de processeurs basse consommation séparée des processeurs pour ''desktop''. Les processeurs Atom ont utilisé plusieurs microarchitectures succesives. La microarchitecture ''Bonnell'' et son ''die shrink'' ''Saltwell'' a été la première. C'était une microarchitecture sans exécution dans le désordre. Les microarchitectures qui suivent sont, dans l'ordre chronologique et en 2026 : ''Silvermont'', ''Goldmont'', ''Tremont'' et ''Gracemont''. Elles utilisent toutes l'exécution dans le désordre.
Outre les processeurs Atom, Intel utilise aussi ces microarchitectures basse consommation dans ses processeurs pour ''desktop''. De nos jours, les processeurs Intel utilisent les deux types de micro-architectures en même temps. Les CPU Intel modernes disposent de deux types de coeurs : les coeurs P et le coeurs E. Leur nom signifie "Performance" et "Efficient", qui trahissent leur but. Les coeurs P utilisent la microarchitecture de la lignée principale, qui est conçue pour la performance. Les coeurs E, quant à eux, utilisent les micro-architectures basse consommation de la lignée secondaire. LA technique est aussi utilisée par les processeurs ARM et quelques autres marques.
Un autre détail est que les microarchitecture basse consommation ne mettent pas le paquet sur les instructions SIMD. Elles se contentent d'utiliser des unités de calcul 128 bits. Pour l'AVX, qui utilise des registres de 256 bits, ,voire 512, les calculs sont fait en deux fois. La conséquence est qu'une instruction SIMD de type SSE ou AVX, est décodée en deux micro-opérations 128 bits.
==Les processeurs Atom d'Intel, de microarchitecture Bonnell==
L'architecture de l'Atom première génération est assez simple. Son pipeline faisait 16 étages, ce qui est beaucoup. C'est un processeur 32 bits, ce qui aura son importance dans ce qui suit. Il était conçu pour être un processeur basse consommation, donc peu puissant. En conséquence, il n'a pas d'exécution dans le désordre, même s'il est superscalaire. C'était la norme à l'époque pour les processeurs basse consommation, que de faire sans exécution dans le désordre. De nos jours, les choses ont bien changée, même les processeurs basse consommation ont exécution dans le désordre, superscalarité et renommage de registres.
===Le ''front-end'' de l'Atom===
Le cache d'instruction permet de lire 8 octets par cycle, qui sont placés dans une file d'instruction, elle-même suivie par deux décodeurs. Le fait que les décodeurs lisent les instructions depuis une file d'instruction fait que les deux instructions décodées ne sont pas forcément consécutives en mémoire RAM. Par exemple, l'Atom peut décoder un branchement prédit comme pris, suivi par l'instruction de destination du branchement. Les deux instructions ont été chargées dans la file d'instruction et sont consécutifs dedans, alors qu'elles ne sont pas consécutives en mémoire RAM.
Sur l'Atom, la majorité des instructions x86 sont décodées en une seule micro-opération, y compris les instructions ''load-up''. Le microcode n'est utilisé que pour une extrême minorité d'instructions et est à part des deux décodeurs précédents. L'avantage est que cela permet d'utiliser au mieux la file de micro-opération, qui est de petite taille. Mais surtout, cela permet de grandement réduire la consommation du processeur, au détriment de ses performances.
Pour avoir un décodage rapide, malgré des instructions complexes, le processeur recourt à la technique du pré-décodage, qui prédécode les instructions lors de leur chargement dans le cache d'instruction. Le prédécodage lui-même prend deux cycles, là où une lecture dans le L1 d'instruction en prend 3. les défauts de cache d'instruction sont donc plus longs de deux cycles. Mais l'avantage du prédécodage est que la consommation d'énergie est diminuée. Prenez une instruction exécutée plusieurs fois, dans une boucle. Au lieu de décoder intégralement une instruction à chaque fois qu'on l'exécute, on la prédécode une fois, seul le reste du décodage est fait à chaque exécution. D'où un gain d'énergie assez intéressant. Les caches de micro-opération, qui sont capables d'exécuter une optimisation similaire, n'existaient pas encore à cette époque.
===Le chemin de données de l'Atom===
Les deux décodeurs alimentent une file de micro-opérations de petite taille : 32 µops maximum, 16 par ''thread'' si le ''multithreading'' matériel est activé. La file de micro-opérations a deux ports d'émission, ce qui permet d'émettre au maximum 2 µops par cycle. Les conditions pour cela sont cependant drastiques. Les deux instructions ne doivent pas avoir de dépendances de registres, à quelques exceptions près liées au registre d'état. Le multithreading matériel doit aussi être désactivé. Les deux instructions doivent aller chacun dans un port différent, et cela tient en compte du fait que les deux ports sont reliés à des unités de calcul fort différentes. Le tout est illustré ci-dessous.
Les deux ports ont chacun une ALU simple dédiée, capable de faire des additions/soustractions, des opérations bit à bit et des copies entre registres. Mais ils ont aussi des opérations qui leur sont spécifiques. La séparation entre les deux pipelines est assez complexe. Il ne s'agit pas du cas simple avec un pipeline entier et un pipeline flottant séparés. En réalité, il y a deux pipelines, chacun capables de faire des opérations entières et flottantes, mais pas les mêmes opérations.
Le premier port permet d’exécuter des opérations entières simples, une addition flottante, des comparaisons/branchements, ou une instruction de calcul d'adresse LEA. Le second port/pipeline est, quant à lui, conçu pour exécuter les instruction ''load-up'' nativement, en une seule micro-opération. Il contient toute la machinerie pour faire les accès mémoire, notamment des unités de calcul d'adresse et un cache L1 de données. A la suite du cache, se trouvent une ALU entière simple, un ''barrel shifter'', et un circuit multiplieur/diviseur. Le circuit multiplieur/diviseur est utilisé à la fois pour les opérations flottantes et entières.
[[File:Intel Atom Microarchitecture.png|centre|vignette|upright=2.5|Intel Atom Microarchitecture]]
Cette organisation difficile à comprendre est en réalité très efficace, très économe en circuit, tout en gardant une performance intéressante. Les instructions simples, ADD/SUB/bitwise sont supportées dans les deux pipelines. Il faut dire que ce sont des opérations courantes qu'il vaut mieux optimiser au mieux. Le processeur peut donc émettre deux opérations simples et fréquentes en même temps, ce qui augmente les performances.
Les opérations plus complexes, à savoir les multiplications/divisions/décalages/rotations/manipulations de bit sont supportées dans un seul pipeline. La raison est qu'il est rare que de telles opérations soient consécutives, et qu'il n'est donc pas utile d'optimiser pour cette situation. Si les deux pipelines devaient supporter ces opérations, cela demanderait de dupliquer les circuits multiplieurs/diviseur, ce qui aurait un cout en circuit important pour un gain en performance assez faible.
===Le système d'exceptions flottantes de l'Atom===
Le processeur étant sans exécution dans le désordre, ses instructions doivent écrire dans les registres dans l'ordre du programme. En conséquence, certaines instructions doivent être retardées, leur émission doit attendre que les conditions soient adéquates. Et cela pose problème avec les opérations flottantes, vu qu'elles prennent pas mal de cycles pour s'exécuter. Imaginez qu'une instruction flottante de 10 cycles soit suivie par une instruction entière. En théorie, on doit retarder l'émission de l'instruction entière de 9 cycles pour éviter tout problèmes. Le cout en performance est donc assez important.
En théorie, les instructions entières et flottantes écrivant dans des registres séparés, ce qui fait que l'on pourrait exécuter instructions entières et flottantes dans le désordre. Sauf pour les instructions de copie entre registres entier et flottants, mais laissons-les de côté. Le problème est qu'une instruction flottante peut parfois lever une exception, par exemple en cas de division par zéro, ou pour certains calculs précis. Si une exception est levée, alors l'instruction flottante est annulée, de même que toutes les instructions qui suivent, y compris les opérations entières.
Ce n'est pas un problème si le processeur gère nativement les exceptions précises, par exemple avec un tampon de ré-ordonnancement. Mais l'Atom étant un processeur sans exécution dans le désordre, les instructions entières devraient être mises en attente tant qu'une instruction flottante est en cours d'exécution. Heureusement, l'Atom d'Intel a trouvé une parade. La technique, appelée ''Safe Instruction Recognition'' par Intel, est décrite dans le brevet US00525721.6A.
L'idée est de tester les opérandes flottantes, pour détecter les combinaisons d'opérandes à problème, dont l'addition/multiplication peut lever une exception. Si des opérandes à problème sont détectées, on stoppe l'émission de nouvelles instructions en parallèle de l'instruction flottante et l'unité d'émission émet des bulles de pipeline tant que l'instruction flottante est en cours. Sinon, l'émission multiple fonctionne. La technique permet ainsi de ne pas écrire dans les registres entiers/flottants dans l'ordre du programme : une instruction entière peut être autorisée à s'exécuter même si elle écrit dans un registre entier avant qu'une instruction flottante délivre son résultat.
==Les microarchitectures de Silvermont à Crestmont==
Les microarchitectures qui font suite à l'architecture Bonnell sont, dans l'ordre chronologique : Silvermont, Goldmont, Tremont, Gracemont et Crestmont. Elles utilisent toute l'exécution dans le désordre, avec renommage de registres. Elles sont aussi superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Les différentes microarchitecture sont toutes superscalaires, avec une progression chronologique de la triple vers la pentuple émission, voire au-delà. Il faut noter que la microarchitecture Tremont pouvait décoder six instructions par cycles, mais émettre seulement 4 µops par cycle. Idem avec Gracemont , qui était limitée à 5 µops par cyle, là encore à cause de l’unité de renommage.
{|class="wikitable"
|-
! Processeur !! Chargement !! Décodage !! Renommage
|-
! Silvermont
| 16 octets par cycle || 2 instructions par cycle || 2 µops par cycle
|-
! Goldmont
| 16 octets par cycle || 3 instructions/cycle || 2 µops par cycle
|-
! Tremont
| 32 octets par cycle || 6 instructions/cycle || 4 µops par cycle
|-
! Gracemont
| 32 octets par cycle || 6 instructions/cycle || 5 µops par cycle
|-
! Crestmont
| 6 octets par cycle || 6 instructions par cycle || 6 µops par cycle
|}
Les microarchitectures Tremont, Gracemont et Crestmont sont des processeurs superscalaires assez larges, ce qui est assez étonnant pour de la basse consommation, mais qui est devenu la norme depuis longtemps, grâce à la loi de Moore. Néanmoins, cela vient avec des optimisations pour réduire le cout en circuit/énergie. Et ces optimisations sont intéressantes à étudier.
Une première optimisation est l'utilisation de '''stations de réservations décentralisées'''. Et quand je dis décentralisé, c'est au point où chaque ALU entière a sa propre station de réservation. Cela réduit les performances par rapport à une station de réservation centralisée, ou un moins un système moins décentralisé, mais entraine un gain en consommation d'électricité non négligeable.
Une seconde optimisation est liée aux stations de réservation flottantes. Ou plutôt devrais-je dire : la station de réservation flottante. Car si les ALU entières oint chacune leur propre station de réservation,
Les unités flottantes ont quant à elle une station de réservation partagée entre toutes les FPUs. La raison à cela est que cela convient mieux aux opérations flottantes. Elles ont une durée plus longue, attendent plus longtemps dans les stations de réservation, etc. bref, des détails techniques qui font que c'est mieux ainsi.
Une troisième optimisation, liée à la précédente, est l'usage de '''files d'instructions pour les opérations mémoire'''. Si ces processeurs utilisent des stations de réservation pour les opérations entières, ce n'est pas le cas pour les opérations mémoire. Les accès mémoire sont donc exécutés dans l'ordre, contrairement aux autres opérations. Il arrive aussi que ce soit la même chose, mais pour les opérations flottantes, encore que ce soit plus rare. . Les performances mémoire sont donc un peu réduites, mais le gain en termes d'énergie en vaut la peine.
Une particularité de ces microarchitectures est l'absence du cache de micro-opérations. Pourtant, un tel cache devrait être une source d'économies d'énergie. Lire des µops dans ce cache évite d'avoir à accéder au cache d’instruction, décoder des instructions, etc. Sans doute que le cout en circuit est plus faible, et que ce gain en circuit est considéré comme plus important que les économies d'énergie et les gains en performances qui vont avec.
===Les unités fonctionnelles et l'exécution dans le désordre===
Pour ce qui est de l'exécution dans le désordre, il faut préciser que Silvermont et Goldmont n'utilisent pas le même mécanisme de renommage de registre. Silvermont fait le renommage dans le ROB, avec un banc de registre architectural et un banc de registres renommés. L'avantage est que cette implémentation est économe en circuits, même si elle entraine des déplacements de données et des copies entre registres réguliers. Elle permet notamment de réduire le nombre de ports du banc de registre, en répartissant ceux-ci sur le banc de registre architectural et renommés. Goldmont passe à un renommage avec un banc de registre physique unique.
Silvermont a deux ALU entières, un ''barrel shifter'', un multiplieur entier, une unité mémoire unique, un additionneur flottant et un multiplier flottant. Le tout est repartit sur 5 pseudo-ports d'émission, en réalité 5 stations de réservation de 8 entrées, chacune connectée à quelques unités de calcul. Le tout est organisé comme suit
{|class="wikitable"
|+ Silvermont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5
|-
| 8 entrées || 8 entrées || 6 entrées || 8 entrées || 8 entrées
|-
| colspan="5" |
|-
| ALU entière || ALU entière || LOAD/STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || ALU SIMD entière (128bits) || ALU SIMD entière (128bits)
|-
| || Branchements || ||
|}
Goldmont fait les modifications suivantes : il rajoute une ALU entière, les stations de réservations augmentent en taille, il fusionne les stations de réservation pour les unités flottantes.
De plus, l'ALU pour les branchements reçoit sa propre réservation de station. L'avantage est que cela permet d'exécuter les branchements en priorité, et dont de savoir s'ils sont pris ou pas en avance, et à quelle adresse brancher cas échant. Le plus tôt ces informations sont connues, au mieux la prédiction de branchement fonctionne bien, au mieux on peut annuler les instructions chargées à tord.
{|class="wikitable"
|-
|+ Goldmont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8
|-
| 10 entrées || 10 entrées || 10 entrées || 8 entrées || colspan="2" | 13 entrées || colspan="2" | 27 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || STORE || FADD || FMUL
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || ALU SIMD entière (128bits) || ALU SIMD entière + MUL SIMD entière (128bits)
|}
Les microarchitectures suivantes ont plus d'unités de calcul.
{|class="wikitable"
|-
|+ Tremont
|-
! Station de réservation 1 !! Station de réservation 2 !! Station de réservation 3 !! Station de réservation 4 !! Station de réservation 5 !! Station de réservation 6 !! Station de réservation 7 !! Station de réservation 8 !! Station de réservation 9 !! Station de réservation 10
|-
| 12 entrées || 12 entrées || 7 entrées || 17 entrées || colspan="2" | 13 entrées || 14 entrées || 13 entrées || colspan="2" | 24 entrées
|-
| colspan="8" |
|-
| ALU entière || ALU entière || ALU entière || Branchements || LOAD || LOAD || STORE || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| ''Barrel shfiter'' || Multiplieur entier || || || || || SIMD FMUL (128bits) || SIMD FADD (128bits)
|-
| || || || || || || FDIV ||
|}
{|class="wikitable"
|-
|+ Gracemont
|-
! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS !! RS
|-
| 15 entrées || 16 entrées || 16 entrées || 15 entrées || colspan="2" | 42 entrées
| colspan="4" | 22 entrées
| 13 entrées || colspan="2" | 24 entrées
|-
| ALU || ALU || ALU || ALU || Branch || Branch
| LOAD || LOAD || STORE || STORE
| FSTORE (écriture) || FSTORE (écriture) || ALU SIMD (128 bits) || ALU SIMD (128 bits) || ALU SIMD (128 bits)
|-
| || MUL entier || MUL entier || || ||
| || || ||
| SIMD FMA (128bits) || SIMD FMA (128bits)
|-
| || DIV entier || DIV entier || || ||
| || || ||
| SIMD FADD (128bits) || SIMD FADD (128bits)
|-
| || LEA || Manipulation de bits || || ||
| || || ||
| SIMD FMUL (128bits) || SIMD FMUL (128bits)
|}
===L'unité mémoire et la FPU des CPU Intel basse consommation===
Silvermont et Goldmont se limitent à une simple unité mémoire, qui a cependant deux ports séparés pour Goldmont. L'économie en circuit est conséquente, car ils se débrouillent avec une ''load queue'' et une ''Store queue'', toutes deux simple port (si on omet le port pour le ''store to load forwarding''). De même, le cache L1 peut se débrouiller avec seulement une implémentation naturelle, à savoir un port de lecture et un d'écriture (le cache est naturellement double port, vu que les entrées de lecture et d'écriture sont séparées sur les cellules de SRAM). Pas besoin d'utiliser de structures matérielle avec un port de plus que de nature, ce qui est un énorme avantage en termes d'économie de circuits et de consommation énergétique.
C'est sur les microarchitecture suivantes que démarrent les hostilités. Tremont ajoute un second port pour émettre une lecture, Gracemont ajoute un second port pour les écritures en plus (4 ports au total). Crestmont uant à elle, dispose de trois ports d'émission pour les lectures et de deux ports d'émission pour les écritures.
De plus, la file de µops mémoire est scindée en deux : une file de µops mémoire, suivie par une station de réservation de petite taille. Les µops mémoire sont accumulées dans la file de µop quand les stations de réservation sont pleines, histoire de ne pas bloquer l'émission en cas de défaut de cache un peu trop prolongé. Prenons une µop mémoire qui est tout juste ''dispatch'' : l'unité de ''dispatch'' regarde si il y a de la place dans la station de réservation. Si c'est le cas, elle envoie la µop mémoire à la station de réservation. Sinon, elle l'envoie dans la file de µops mémoire.
Tremont utilise une file de µops de 21 entrées, alors que la station de réservation en fait 13, ce qui fait un total de 34 entrées. Les performances sont moindres comparé à une station de réservation de 34 entrées, car les µops dans la file de µops mémoire ne peuvent pas être émises tant qu'elles n'ont pas atteint les stations de réservations, même si les conditions sont remplies en matière d'opérandes pour qu'elles puissent passer devant. Mais le gain en circuits est conséquent, de même que l'économie d'énergie, pour une perte de performance plus que tolérable.
Les opérations flottantes étant assez longues, Gracemont utilise le même système pour les opérations flottantes. Une file de µops précède deux stations de réservation. La première accumule des opérations flottantes, destinées à être exécutées dans la FPU (de type SIMD). La file de µops fait 56 entrées, alors que la station de réservation en a 35. Une seconde station de réservation de 18 entrées met en attente les écritures flottantes, à savoir les écritures qui écrivent un flottant simple ou double précision en mémoire RAM.
Le schéma ci-dessous illustre ce qu'il en est sur l'architecture Crestmont. On voit que les files pour µops flottantes et mémoire sont de type ''non-scheduling'', ce qui veut dire que ce sont de simples files d'instruction.
[[File:Crestmont.png|centre|vignette|upright=3|Crestmont]]
===Le ''front-end'' : un changement après Silvermont et Goldmont===
Le ''front-end'' commence avec le cache d'instruction et la file d'instruction. Le cache d'instruction est un cache simple port, capable de lire 16 octets consécutifs par cycle. Ces 16 octets peuvent contenir un nombre variable d'instructions, de 16 instructions maximum à une seule (une instruction x86 fait entre 1 et 16 octets). Sur l'architecture Silvermont, la file d'instruction peut mémoriser maximum 6 blocs de 16 octets, elle a donc une capacité de 6 blocs.
La file d'instruction est suivie par plusieurs décodeurs simples et un microcode. L'architecture Silvermont n'a que deux décodeurs simples, alors que Goldmont en a 3. Le décodage d'une instruction prend trois cycles d'horloge, le décodage est évidemment pipeliné. Il faut noter que les instructions ''load-op'' sont décodées en une seule µop, ils utilisent la technique de la micro-fusion. Ils alimentent une file de µops, capable de mémoriser 32 µops sur Silvermont.
{|class="wikitable"
|-
! !! Silvermont !! Goldmont
|-
! Chargement
| colspan="2" | 16 octets par cycle
|-
! File d'instruction
| 6 blocs, donc 6* 16 octets ||
|-
! Decodage
| Deux instructions par cycle || Trois instructions par cycle
|-
! File de µops
| 32 µops ||
|}
En remplacement du cache de µops, ces microarchitectures, depuis Tremont, utilisent un ''front-end'' tel que décrit dans le chapitre sur les processeurs superscalaires, dans la section "''Les optimisations des processeurs superscalaires larges''". Faisons un rapide rappel, avec une remise en contexte "historique".
Goldmont était une microarchitecture triple émission, avec trois décodeurs simples (et un microcode). La microarchitecture suivante, Tremont, souhaitait augmenter la largeur du processeur. Idéalement, il aurait fallu recréer le ''front-end'' pour pouvoir charger 4 à 5 instructions simultanément, et surtout les décoder. Mais ce n'est pas ce qui a été fait. A la place, elles ont décidées de dupliquer les décodeurs, et de les coller ensemble en utilisant l'unité de prédiction de branchement.
Il y a donc 6 décodeurs, qui sont organisés en deux paquets de trois, en deux ''clusters''. Et cette organisation en ''cluster'' est primordiale pour comprendre la suite. Le processeur ne charge pas 6 instructions consécutives, mais 2 blocs de 3 instructions. Les deux blocs ne sont donc pas consécutifs en mémoire RAM, bien au contraire : ils sont séparés par des branchements. Le premier bloc est chargé normalement, mais le second bloc est décidé par la prédiction de branchement. La conséquence est qu'en absence de branchements, le processeur ne peut que charger/décoder 3 instructions consécutives, pas 6. Pour charger deux blocs non-consécutifs, le cache d'instruction est un cache double port.
Notons que ces ''clusters'' ont leur propre file d'instruction, et leur propre file de µops ! Il y a donc deux files de µops en sortie des deux ''clusters'' de décodeurs. L'unité de renommage choisit à chaque cycle quelle file de µops utiliser, suivant leur remplissage ou d'autres informations. Sur Tremont, l'unité de renommage peut renommer 4 µops, même si le processeur peut décoder 6 instructions en parallèle dans le meilleur des cas. Mais ce n'est pas un problème énorme, car le processeur n'atteint pas toujours les 6 µops par cycle. Il oscille entre 3 et 6 µops par cycle, parfois moins si les conditions ne sont pas réunies. Les files de µops découplent décodeurs et unités de renommages, histoire de fluidifier le trafic.
[[File:Front-end découplé de la microarchitecture Tremont.png|centre|vignette|upright=3|Front-end découplé de la microarchitecture Tremont.]]
La microarchitecture '''Gracemont''' peut décoder deux paquets de 3 instructions chaque. Elle envoie ensuite 5 µops à l'unité d'émission, qui sont distribuées dans deux pipelines : un pipeline flottant/SIMD et un pipeline entier. Et le pipeline entier peut émettre 4 µops entières, deux lectures, deux écritures et deux branchements ! Pire que ça, le ''barrel shifter'' et le multiplieur/diviseur sont dupliqués en deux exemplaires !
[[File:Gracemont.png|centre|vignette|upright=3|Gracemont]]
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les microarchitectures pour le x86 : haute performance
| prevText=Les microarchitectures pour le x86 : haute performance
| next=Les processeurs VLIW et EPIC
| nextText=Les processeurs VLIW et EPIC
}}
</noinclude>
56089m6j6m6lbokjexpgfx0mwhwpoaq
Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86
0
84531
773298
2026-09-27T15:56:57Z
Mewtow
31375
Mewtow a déplacé la page [[Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86]] vers [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance]] : Pour harmoniser avec le chapitre suivant, créé aujourd'hui
773298
wikitext
text/x-wiki
#REDIRECTION [[Fonctionnement d'un ordinateur/Les microarchitectures pour le x86 : haute performance]]
26xnqd3yexce9et09xt57st6f6o492l
Mathc initiation/007e
0
84532
773317
2026-09-27T16:59:05Z
Xhungab
23827
news
773317
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
* [[#X(z) = z/(z+2)^2| '''X(z) = z/(z+2)^2''']]
* [[#X(z) = 2 z/(z+2)^2| '''X(z) = 2 z/(z+2)^2''']]
* [[#X(z) = 4 z/(z+2)^2| '''X(z) = 4 z/(z+2)^2''']]
==X(z) = z/(z+2)^2==
On veut retrouver x[n]
X(z) = z/(z + 2 )^2
X(z) = z/(z-(-2))^2
x[n] = n (-2)^(n-1) u[n]
==X(z) = 2 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 2 z/(z + 2 )^2
X(z) = 2 z/(z-(-2))^2
x[n] = 2 n (-2)^(n-1) u[n]
x[n] = -(-2) n (-2)^(n-1) u[n]
x[n] = - n (-2)^n u[n]
==X(z) = 4 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 4 z/(z + 2 )^2
X(z) = 4 z/(z-(-2))^2
x[n] = 4 n (-2)^(n-1) u[n]
x[n] = 2 2 n (-2)^(n-1) u[n]
x[n] = (-(-2)) (-(-2)) n (-2)^(n-1) u[n]
x[n] = (-2) (-2) n (-2)^(n-1) u[n]
x[n] = n (-2)^(n+1) u[n]
{{AutoCat}}
s8py27gms7xjyozs6pswi9553c13cly
773319
773317
2026-09-27T17:00:07Z
Xhungab
23827
773319
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a|Sommaire]]
* [[#X(z) = z/(z+2)^2| '''X(z) = z/(z+2)^2''']]
* [[#X(z) = 2 z/(z+2)^2| '''X(z) = 2 z/(z+2)^2''']]
* [[#X(z) = 4 z/(z+2)^2| '''X(z) = 4 z/(z+2)^2''']]
==X(z) = z/(z+2)^2==
On veut retrouver x[n]
X(z) = z/(z + 2 )^2
X(z) = z/(z-(-2))^2
x[n] = n (-2)^(n-1) u[n]
==X(z) = 2 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 2 z/(z + 2 )^2
X(z) = 2 z/(z-(-2))^2
x[n] = 2 n (-2)^(n-1) u[n]
x[n] = -(-2) n (-2)^(n-1) u[n]
x[n] = - n (-2)^n u[n]
==X(z) = 4 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 4 z/(z + 2 )^2
X(z) = 4 z/(z-(-2))^2
x[n] = 4 n (-2)^(n-1) u[n]
x[n] = 2 2 n (-2)^(n-1) u[n]
x[n] = (-(-2)) (-(-2)) n (-2)^(n-1) u[n]
x[n] = (-2) (-2) n (-2)^(n-1) u[n]
x[n] = n (-2)^(n+1) u[n]
{{AutoCat}}
d5beq82zyofnxvqca94f140ebkywxpz
773323
773319
2026-09-27T17:02:16Z
Xhungab
23827
773323
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z+2)^2| '''X(z) = z/(z+2)^2''']]
* [[#X(z) = 2 z/(z+2)^2| '''X(z) = 2 z/(z+2)^2''']]
* [[#X(z) = 4 z/(z+2)^2| '''X(z) = 4 z/(z+2)^2''']]
==X(z) = z/(z+2)^2==
On veut retrouver x[n]
X(z) = z/(z + 2 )^2
X(z) = z/(z-(-2))^2
x[n] = n (-2)^(n-1) u[n]
==X(z) = 2 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 2 z/(z + 2 )^2
X(z) = 2 z/(z-(-2))^2
x[n] = 2 n (-2)^(n-1) u[n]
x[n] = -(-2) n (-2)^(n-1) u[n]
x[n] = - n (-2)^n u[n]
==X(z) = 4 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 4 z/(z + 2 )^2
X(z) = 4 z/(z-(-2))^2
x[n] = 4 n (-2)^(n-1) u[n]
x[n] = 2 2 n (-2)^(n-1) u[n]
x[n] = (-(-2)) (-(-2)) n (-2)^(n-1) u[n]
x[n] = (-2) (-2) n (-2)^(n-1) u[n]
x[n] = n (-2)^(n+1) u[n]
{{AutoCat}}
0utl8zgezucbr6hkz6bkn078pouci3t
773431
773323
2026-09-28T10:38:12Z
Xhungab
23827
773431
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z+2)^2| '''X(z) = z/(z+2)^2''']]
* [[#X(z) = 2 z/(z+2)^2| '''X(z) = 2 z/(z+2)^2''']]
* [[#X(z) = 4 z/(z+2)^2| '''X(z) = 4 z/(z+2)^2''']]
==X(z) = z/(z+2)^2==
On veut retrouver x[n]
X(z) = z/(z + 2 )^2
X(z) = z/(z-(-2))^2
x[n] = n (-2)^(n-1) u[n]
==X(z) = 2 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 2 z/(z + 2 )^2
X(z) = 2 z/(z-(-2))^2
x[n] = 2 n (-2)^(n-1) u[n]
x[n] = -(-2) n (-2)^(n-1) u[n]
x[n] = - n (-2)^n u[n]
==X(z) = 4 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 4 z/(z + 2 )^2
X(z) = 4 z/(z-(-2))^2
x[n] = 4 n (-2)^(n-1) u[n]
x[n] = 2 2 n (-2)^(n-1) u[n]
x[n] = (-(-2)) (-(-2)) n (-2)^(n-1) u[n]
x[n] = (-2) (-2) n (-2)^(n-1) u[n]
x[n] = n (-2)^(n+1) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* n (-2)^(n-1)
* - n (-2)^n
* n (-2)^(n+1)
{{AutoCat}}
paied6za4hwvosw7bewqisyb7fqlb1g
773436
773431
2026-09-28T10:45:14Z
Xhungab
23827
773436
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z+2)^2| '''X(z) = z/(z+2)^2''']]
* [[#X(z) = 2 z/(z+2)^2| '''X(z) = 2 z/(z+2)^2''']]
* [[#X(z) = 4 z/(z+2)^2| '''X(z) = 4 z/(z+2)^2''']]
==X(z) = z/(z+2)^2==
On veut retrouver x[n]
X(z) = z/(z + 2 )^2
X(z) = z/(z-(-2))^2
x[n] = n (-2)^(n-1) u[n]
==X(z) = 2 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 2 z/(z + 2 )^2
X(z) = 2 z/(z-(-2))^2
x[n] = 2 n (-2)^(n-1) u[n]
x[n] = -(-2) n (-2)^(n-1) u[n]
x[n] = - n (-2)^n u[n]
==X(z) = 4 z/(z+2)^2==
On veut retrouver x[n]
X(z) = 4 z/(z + 2 )^2
X(z) = 4 z/(z-(-2))^2
x[n] = 4 n (-2)^(n-1) u[n]
x[n] = 2 2 n (-2)^(n-1) u[n]
x[n] = (-(-2)) (-(-2)) n (-2)^(n-1) u[n]
x[n] = (-2) (-2) n (-2)^(n-1) u[n]
x[n] = n (-2)^(n+1) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* n (-2)^(n-1)
* - n (-2)^n
* n (-2)^(n+1)
{{AutoCat}}
hs3i1byp1kyxojqu747psdzjksegbq7
Livre de cuisine/Egg rolls
0
84533
773354
2026-09-27T20:54:46Z
Simon Villeneuve
24400
et oui
773354
wikitext
text/x-wiki
{{livre de cuisine}}
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires fourrées, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*3 litres d’[[huile d’arachide]] (ou végétale) pour la cuisson (prévoir un litre de plus pour une recette doublée ou triplée)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller par-dessus et fermer le pâté.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
fjg3tighhzhcpsv5lq3j7rx7yifkhfc
773355
773354
2026-09-27T20:55:46Z
Simon Villeneuve
24400
image
773355
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires fourrées, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*3 litres d’[[huile d’arachide]] (ou végétale) pour la cuisson (prévoir un litre de plus pour une recette doublée ou triplée)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller par-dessus et fermer le pâté.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
anj80doqve88l06h5okctre3k38w6yi
773358
773355
2026-09-27T21:04:33Z
Simon Villeneuve
24400
mieux
773358
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*3 litres d’[[huile d’arachide]] (ou végétale) pour la cuisson (prévoir un litre de plus pour une recette doublée ou triplée)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller par-dessus et fermer le pâté.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
64mdej7xmol0appdp5c0k1927ozi6wa
773359
773358
2026-09-27T21:05:35Z
Simon Villeneuve
24400
/* Préparation */ retouche
773359
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*3 litres d’[[huile d’arachide]] (ou végétale) pour la cuisson (prévoir un litre de plus pour une recette doublée ou triplée)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié vers le centre, par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller dessus et fermer le pâté.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
rxa296fob09hjqg4m3s4k6nscax0f9y
773360
773359
2026-09-27T21:08:37Z
Simon Villeneuve
24400
/* Préparation */ mieux ?
773360
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*3 litres d’[[huile d’arachide]] (ou végétale) pour la cuisson (prévoir un litre de plus pour une recette doublée ou triplée)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié vers le centre, par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller dessus et fermer le pâté. Aux extrémités, les deux côtés de la pâte se referment l'un sur l'autre sans aucune garniture entre, rendant le rouleau scellé.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
i2dejf78ly9se00xz0qusnmjyrlvmtw
773362
773360
2026-09-27T21:12:56Z
Simon Villeneuve
24400
/* Conservation */ +
773362
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*3 litres d’[[huile d’arachide]] (ou végétale) pour la cuisson (prévoir un litre de plus pour une recette doublée ou triplée)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié vers le centre, par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller dessus et fermer le pâté. Aux extrémités, les deux côtés de la pâte se referment l'un sur l'autre sans aucune garniture entre, rendant le rouleau scellé.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Accompagnements ==
Le ''egg roll'' s'accompagne bien de plusieurs types de sauces, dont notamment la [[sauce au prunes]].
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
jnfham3356bqkpvofno3zjsjxlk8i9p
773363
773362
2026-09-27T21:15:03Z
Simon Villeneuve
24400
/* Hors mélange */ mieux ?
773363
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
*60 pâtes à rouleaux impériaux
=== Hors mélange ===
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*[[Huile d’arachide]] ou [[huile végétale|végétale]] pour la cuisson (la quantité dépend de votre friteuse)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié vers le centre, par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller dessus et fermer le pâté. Aux extrémités, les deux côtés de la pâte se referment l'un sur l'autre sans aucune garniture entre, rendant le rouleau scellé.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Accompagnements ==
Le ''egg roll'' s'accompagne bien de plusieurs types de sauces, dont notamment la [[sauce au prunes]].
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
0stuqmgufb7gw9satmmvbslj1zu9i30
773364
773363
2026-09-27T21:15:29Z
Simon Villeneuve
24400
/* Ingrédients */ retouche
773364
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
=== Hors mélange ===
*60 pâtes à rouleaux impériaux
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*[[Huile d’arachide]] ou [[huile végétale|végétale]] pour la cuisson (la quantité dépend de votre friteuse)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié vers le centre, par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller dessus et fermer le pâté. Aux extrémités, les deux côtés de la pâte se referment l'un sur l'autre sans aucune garniture entre, rendant le rouleau scellé.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Accompagnements ==
Le ''egg roll'' s'accompagne bien de plusieurs types de sauces, dont notamment la [[sauce au prunes]].
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
sljp873dkawkhyor1sx4a4dd63cx32m
773365
773364
2026-09-27T21:15:46Z
Simon Villeneuve
24400
/* Ingrédients */ ou
773365
wikitext
text/x-wiki
{{livre de cuisine}}
[[File:CookbookEggrollTongs.jpg|vignette|''Egg roll'' cuit sortant de la friteuse.]]
Les '''''egg rools''''' sont une sorte de pâtés impériaux réalisés avec des pâtes rectangulaires garnies, repliées et fermées en forme de cylindre aplati, collés avec un peu d’œuf liquide et cuits dans l'huile.
== Ingrédients ==
(pour environ 60 ''eggs roll'')
*6 tasses de chou râpé (environ les 2/3 d’un gros chou)
*4 tasses de poulet cuit et haché (environ un poulet désossé)
*1 tasse d’oignon haché finement
*1 tasse de céleri haché finement
*1 c. à table de sauce soya
*2 c. à table d’huile d’arachide (ou végétale)
*1 à 2 c. à thé de sel (au goût)
*1/4 c. à thé de poivre
=== Hors mélange ===
*60 pâtes à rouleaux impériaux
*1 œuf battu pour la pâte à eggs roll, qui sert de « colle ».
*[[Huile d’arachide]] ou [[huile végétale|végétale]] pour la cuisson (la quantité dépend de votre friteuse)
== Préparation ==
*Mélanger tous les ingrédients dans un grand bol.
*Placer une partie du mélange au centre d'une pâte, en gardant dégagés les bords et en proportionnant pour permettre ensuite la fermeture de la pâte.
*Mouillez les bords de la pâte avec un peu d’œuf battu.
*Replier dans le sens de la longueur un côté de la pâte vers le centre, par-dessus le mélange. Repliez l'autre moitié vers le centre, par-dessus la première afin qu'elle dépasse légèrement celle-ci pour venir se coller dessus et fermer le pâté. Aux extrémités, les deux côtés de la pâte se referment l'un sur l'autre sans aucune garniture entre, rendant le rouleau scellé.
*Appuyez légèrement sur le bord de la pâte ainsi que sur les extrémités pour sceller le rouleau en forme de cylindre aplati.
== Cuisson ==
*Faite frire le rouleau dans l'huile d'arachide jusqu'à ce qu'il devienne légèrement doré.
*Retirez le rouleau de la friteuse, déposez-le sur et recouvrez-le de papiers essuie-tout afin d'absorber l'excédent d'huile.
== Accompagnements ==
Le ''egg roll'' s'accompagne bien de plusieurs types de sauces, dont notamment la [[sauce au prunes]].
== Conservation ==
Les ''egg rolls'' peuvent se conserver plusieurs jours au réfrigérateur. Ils se congèlent également bien pour une consommation ultérieure.
[[Catégorie:Cuisine québécoise]]
[[Catégorie:Cuisine asiatique]]
j0jvpw6wqfv8cf3moauhcw3ho75o5du
Mathc initiation/007f
0
84534
773423
2026-09-28T08:54:33Z
Xhungab
23827
news
773423
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^3| '''X(z) = z/(z-3)^3''']]
==X(z) = z/(z-3)^3==
On veut retrouver x[n]
'''X1(z)''' : Nous allons multiplié par la variable d'évolution n : X0(z) = z/(z-3)
'''X2(z)''' : Nous allons multiplié par la variable d'évolution n^2 : X0(z) = z/(z-3)
Nous allons soustraire X2(z) moins X1(z) .
Cela nous donnera la forme : z/(z-3)^3 multiplié par un coefficient.
Posons 3^n u[n] <-> X0(z) = z/(z-3)
n 3^n u[n] <-> '''X1(z)''' = 3 z/(z-3)^2
n^2 3^n u[n] <-> '''X2(z)''' = 3 z(z+3)/(z-3)^3
(n^2 3^n)u[n] - (n 3^n)u[n] <-> '''X2(z)-X1(z)''' = 18 z/(z-3)^3
(n^2-n) 3^n u[n] <-> X2(z)-X1(z) = '''18''' z/(z-3)^3
Soit '''1/18''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/(2 9) (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 '''3^(-2)''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 (n^2-n) 3^(n'''-2''') u[n] <-> X(z) = z/(z-3)^3
Donc x[n] = (n^2-n)/2 3^(n-2) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* 3^n
* n 3^n X1(z) : Multiplié par la variable d'évolution n
* n^2 3^n X2(z) : Multiplié par la variable d'évolution n^2
* n^2 3^n - n 3^n Soustraire X2(z) moins X1(z)
* (n^2-n) 3^n La solution avec le coefficient
* (n^2-n)/2 3^(n-2) La simplification
{{AutoCat}}
7a0mfjafxwol7hdarl381xshaipf11m
773426
773423
2026-09-28T10:02:26Z
Xhungab
23827
773426
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^3| '''X(z) = z/(z-3)^3''']]
==X(z) = z/(z-3)^3==
On veut retrouver x[n]
'''X1(z)''' : Nous allons multiplié par la variable d'évolution n : X0(z) = z/(z-3)
'''X2(z)''' : Nous allons multiplié par la variable d'évolution n^2 : X0(z) = z/(z-3)
Nous allons soustraire X2(z) moins X1(z) .
Cela nous donnera la forme : z/(z-3)^3 multiplié par un coefficient.
Posons 3^n u[n] <-> X0(z) = z/(z-3)
n 3^n u[n] <-> '''X1(z)''' = 3 z/(z-3)^2
n^2 3^n u[n] <-> '''X2(z)''' = 3 z(z+3)/(z-3)^3
(n^2 3^n)u[n] - (n 3^n)u[n] <-> '''X2(z)-X1(z)''' = 18 z/(z-3)^3
(n^2-n) 3^n u[n] <-> X2(z)-X1(z) = '''18''' z/(z-3)^3
Soit '''1/18''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/(2 9) (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 '''3^(-2)''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 (n^2-n) 3^(n'''-2''') u[n] <-> X(z) = z/(z-3)^3
Donc x[n] = (n^2-n)/2 3^(n-2) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* 3^n
* n 3^n X1(z) : Multiplié par la variable d'évolution n
* n^2 3^n X2(z) : Multiplié par la variable d'évolution n^2
* n^2 3^n - n 3^n Soustraire X2(z) moins X1(z)
* (n^2-n) 3^n La solution avec le coefficient
* (n^2-n)/2 3^(n-2) La solution sans le coefficient
{{AutoCat}}
8ge0d0362w864adg6rcpafs06p4fj2u
773428
773426
2026-09-28T10:18:33Z
Xhungab
23827
773428
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z-3)^3| '''X(z) = z/(z-3)^3''']]
==X(z) = z/(z-3)^3==
On veut retrouver x[n]
'''X1(z)''' : Nous allons multiplié par la variable d'évolution n : X0(z) = z/(z-3)
'''X2(z)''' : Nous allons multiplié par la variable d'évolution n^2 : X0(z) = z/(z-3)
Nous allons soustraire X2(z) moins X1(z) .
Cela nous donnera la forme : z/(z-3)^3 multiplié par un coefficient.
Posons 3^n u[n] <-> X0(z) = z/(z-3)
n 3^n u[n] <-> '''X1(z)''' = 3 z/(z-3)^2
n^2 3^n u[n] <-> '''X2(z)''' = 3 z(z+3)/(z-3)^3
(n^2 3^n)u[n] - (n 3^n)u[n] <-> '''X2(z)-X1(z)''' = 18 z/(z-3)^3
(n^2-n) 3^n u[n] <-> X2(z)-X1(z) = '''18''' z/(z-3)^3
Soit '''1/18''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 '''1/9''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 '''3^(-2)''' (n^2-n) 3^n u[n] <-> X(z) = z/(z-3)^3
1/2 (n^2-n) 3^(n'''-2''') u[n] <-> X(z) = z/(z-3)^3
Donc x[n] = (n^2-n)/2 3^(n-2) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* 3^n
* n 3^n X1(z) : Multiplié par la variable d'évolution n
* n^2 3^n X2(z) : Multiplié par la variable d'évolution n^2
* n^2 3^n - n 3^n Soustraire X2(z) moins X1(z)
* (n^2-n) 3^n La solution avec le coefficient
* (n^2-n)/2 3^(n-2) La solution sans le coefficient
{{AutoCat}}
cdln5gvv08ao0oduj5i94hu5qcb124o
Mathc initiation/007g
0
84535
773429
2026-09-28T10:24:58Z
Xhungab
23827
news
773429
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z+3)^3| '''X(z) = z/(z+3)^3''']]
==X(z) = z/(z+3)^3==
On veut retrouver x[n]
'''X1(z)''' : Nous allons multiplié par la variable d'évolution n : X0(z) = z/(z+3)
'''X2(z)''' : Nous allons multiplié par la variable d'évolution n^2 : X0(z) = z/(z+3)
Nous allons soustraire X2(z) moins X1(z) .
Cela nous donnera la forme : z/(z+3)^3 multiplié par un coefficient.
Posons (-3)^n u[n] <-> X0(z) = z/(z-(-3)) = z/(z+3)
n (-3)^n u[n] <-> '''X1(z)''' = -3 z/(z+3)^2
n^2 (-3)^n u[n] <-> '''X2(z)''' = -3 z(z-3)/(z+3)^3
(n^2 (-3)^n) u[n] - (n (-3)^n) u[n] <-> '''X2(z)-X1(z)''' = 18 z/(z+3)^3
(n^2-n) (-3)^n u[n] <-> X2(z)-X1(z) = '''18''' z/(z+3)^3
Soit :
'''1/18''' (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 '''1/9''' (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 '''3^(-1) 3^(-1)''' (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 (-(-3^(-1))) (-(-3^(-1))) (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 (-3^('''-1''')) (-3^('''-1''')) (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 (n^2-n) (-3)^(n'''-2''') u[n] <-> X(z) = z/(z+3)^3
Donc x[n] = (n^2-n)/2 (-3)^(n-2) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* (-3)^n
* n (-3)^n X1(z) : Multiplié par la variable d'évolution n
* n^2 (-3)^n X2(z) : Multiplié par la variable d'évolution n^2
* n^2 (-3)^n -n (-3)^n Soustraire X2(z) moins X1(z)
* (n^2-n) (-3)^n La solution avec le coefficient
* (n^2-n)/2 (-3)^(n-2) La solution sans le coefficient
{{AutoCat}}
7fg9s3ql0a0sr5h9m3kof7h5uljmgs2
773430
773429
2026-09-28T10:34:29Z
Xhungab
23827
773430
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/007a#Étudions quelques cas :|Sommaire]]
* [[#X(z) = z/(z+3)^3| '''X(z) = z/(z+3)^3''']]
==X(z) = z/(z+3)^3==
On veut retrouver x[n]
'''X1(z)''' : Nous allons multiplié par la variable d'évolution n : X0(z) = z/(z+3)
'''X2(z)''' : Nous allons multiplié par la variable d'évolution n^2 : X0(z) = z/(z+3)
Nous allons soustraire X2(z) moins X1(z) .
Cela nous donnera la forme : z/(z+3)^3 multiplié par un coefficient.
Posons z/(z-(-3)) = z/(z+3)
(-3)^n u[n] <-> X0(z) = z/(z+3)
n (-3)^n u[n] <-> '''X1(z)''' = -3 z/(z+3)^2
n^2 (-3)^n u[n] <-> '''X2(z)''' = -3 (z-3)z/(z+3)^3
(n^2 (-3)^n) u[n] - (n (-3)^n) u[n] <-> '''X2(z)-X1(z)''' = 18 z/(z+3)^3
(n^2-n) (-3)^n u[n] <-> X2(z)-X1(z) = '''18''' z/(z+3)^3
Soit :
'''1/18''' (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 '''1/9''' (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 '''3^(-1) 3^(-1)''' (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 (-(-3^(-1))) (-(-3^(-1))) (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 (-3^('''-1''')) (-3^('''-1''')) (n^2-n) (-3)^n u[n] <-> X(z) = z/(z+3)^3
1/2 (n^2-n) (-3)^(n'''-2''') u[n] <-> X(z) = z/(z+3)^3
Donc x[n] = (n^2-n)/2 (-3)^(n-2) u[n]
Vérifions avec [https://www.wolframalpha.com/input?i=Z+transform+calculator Mathematica] :
* (-3)^n
* n (-3)^n X1(z) : Multiplié par la variable d'évolution n
* n^2 (-3)^n X2(z) : Multiplié par la variable d'évolution n^2
* n^2 (-3)^n -n (-3)^n Soustraire X2(z) moins X1(z)
* (n^2-n) (-3)^n La solution avec le coefficient
* (n^2-n)/2 (-3)^(n-2) La solution sans le coefficient
{{AutoCat}}
rdi2wgfo38njz6d6fenmz5oyc7k5zpq